Add util record for describing chunk pos in a level.

Adjust block pos one to match (also use a weak level ref).
This commit is contained in:
Florian Nücke
2022-02-06 01:45:38 +01:00
parent 6ad13b9c02
commit 6fbc455c2f
10 changed files with 139 additions and 93 deletions

View File

@@ -4,23 +4,21 @@ package li.cil.oc2.common.bus;
import li.cil.oc2.api.bus.BlockDeviceBusElement;
import li.cil.oc2.api.bus.DeviceBusElement;
import li.cil.oc2.common.util.ChunkLocation;
import li.cil.oc2.common.util.ChunkUtils;
import li.cil.oc2.common.util.ServerScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
public final class BlockEntityDeviceBusController extends CommonDeviceBusController {
private final Runnable onBusChunkLoadedStateChanged = this::scheduleBusScan;
private final HashSet<TrackedChunk> trackedChunks = new HashSet<>();
private final HashSet<ChunkLocation> trackedChunks = new HashSet<>();
private final BlockEntity blockEntity;
///////////////////////////////////////////////////////////////////
@@ -35,10 +33,9 @@ public final class BlockEntityDeviceBusController extends CommonDeviceBusControl
@Override
public void setDeviceContainersChanged() {
super.setDeviceContainersChanged();
for (final TrackedChunk trackedChunk : trackedChunks) {
trackedChunk.tryGetLevel().ifPresent(level -> {
ChunkUtils.setLazyUnsaved(level, trackedChunk.position());
});
for (final ChunkLocation trackedChunk : trackedChunks) {
trackedChunk.tryGetLevel().ifPresent(level ->
ChunkUtils.setLazyUnsaved(level, trackedChunk.position()));
}
}
@@ -61,30 +58,30 @@ public final class BlockEntityDeviceBusController extends CommonDeviceBusControl
return;
}
final HashSet<TrackedChunk> newTrackedChunks = new HashSet<>();
final HashSet<ChunkLocation> newTrackedChunks = new HashSet<>();
for (final DeviceBusElement element : getElements()) {
if (element instanceof final BlockDeviceBusElement blockElement) {
final LevelAccessor elementLevel = blockElement.getLevel();
final BlockPos elementPosition = blockElement.getPosition();
if (elementLevel != null) {
newTrackedChunks.add(TrackedChunk.of(elementLevel, elementPosition));
newTrackedChunks.add(TrackedChunk.of(elementLevel, elementPosition.relative(Direction.NORTH)));
newTrackedChunks.add(TrackedChunk.of(elementLevel, elementPosition.relative(Direction.EAST)));
newTrackedChunks.add(TrackedChunk.of(elementLevel, elementPosition.relative(Direction.SOUTH)));
newTrackedChunks.add(TrackedChunk.of(elementLevel, elementPosition.relative(Direction.WEST)));
newTrackedChunks.add(ChunkLocation.of(elementLevel, elementPosition));
newTrackedChunks.add(ChunkLocation.of(elementLevel, elementPosition.relative(Direction.NORTH)));
newTrackedChunks.add(ChunkLocation.of(elementLevel, elementPosition.relative(Direction.EAST)));
newTrackedChunks.add(ChunkLocation.of(elementLevel, elementPosition.relative(Direction.SOUTH)));
newTrackedChunks.add(ChunkLocation.of(elementLevel, elementPosition.relative(Direction.WEST)));
}
}
}
// Do not track the chunk the controller itself is in -- this is unneeded because
// we expect the controller to be disposed if its chunk is unloaded.
newTrackedChunks.remove(TrackedChunk.of(level, blockEntity.getBlockPos()));
newTrackedChunks.remove(ChunkLocation.of(level, blockEntity.getBlockPos()));
final HashSet<TrackedChunk> removedChunks = new HashSet<>(trackedChunks);
final HashSet<ChunkLocation> removedChunks = new HashSet<>(trackedChunks);
removedChunks.removeAll(newTrackedChunks);
removeListeners(removedChunks);
final HashSet<TrackedChunk> addedChunks = new HashSet<>(newTrackedChunks);
final HashSet<ChunkLocation> addedChunks = new HashSet<>(newTrackedChunks);
newTrackedChunks.removeAll(trackedChunks);
addListeners(addedChunks);
@@ -94,31 +91,21 @@ public final class BlockEntityDeviceBusController extends CommonDeviceBusControl
///////////////////////////////////////////////////////////////////
private void addListeners(final Collection<TrackedChunk> trackedChunks) {
for (final TrackedChunk trackedChunk : trackedChunks) {
private void addListeners(final Collection<ChunkLocation> trackedChunks) {
for (final ChunkLocation trackedChunk : trackedChunks) {
trackedChunk.tryGetLevel().ifPresent(level -> {
ServerScheduler.scheduleOnLoad(level, trackedChunk.position, onBusChunkLoadedStateChanged);
ServerScheduler.scheduleOnUnload(level, trackedChunk.position, onBusChunkLoadedStateChanged);
ServerScheduler.scheduleOnLoad(level, trackedChunk.position(), onBusChunkLoadedStateChanged);
ServerScheduler.scheduleOnUnload(level, trackedChunk.position(), onBusChunkLoadedStateChanged);
});
}
}
private void removeListeners(final Collection<TrackedChunk> trackedChunks) {
for (final TrackedChunk trackedChunk : trackedChunks) {
private void removeListeners(final Collection<ChunkLocation> trackedChunks) {
for (final ChunkLocation trackedChunk : trackedChunks) {
trackedChunk.tryGetLevel().ifPresent(level -> {
ServerScheduler.cancelOnLoad(level, trackedChunk.position, onBusChunkLoadedStateChanged);
ServerScheduler.cancelOnUnload(level, trackedChunk.position, onBusChunkLoadedStateChanged);
ServerScheduler.cancelOnLoad(level, trackedChunk.position(), onBusChunkLoadedStateChanged);
ServerScheduler.cancelOnUnload(level, trackedChunk.position(), onBusChunkLoadedStateChanged);
});
}
}
private record TrackedChunk(WeakReference<LevelAccessor> level, ChunkPos position) {
public static TrackedChunk of(final LevelAccessor level, final BlockPos position) {
return new TrackedChunk(new WeakReference<>(level), new ChunkPos(position));
}
public Optional<LevelAccessor> tryGetLevel() {
return Optional.ofNullable(level.get());
}
}
}

View File

@@ -4,14 +4,13 @@ package li.cil.oc2.common.bus.device.rpc.item;
import li.cil.oc2.api.bus.device.object.Callback;
import li.cil.oc2.api.bus.device.object.Parameter;
import li.cil.oc2.common.util.Location;
import li.cil.oc2.common.util.BlockLocation;
import li.cil.oc2.common.util.TickUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.LevelAccessor;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
@@ -27,12 +26,12 @@ public final class SoundCardItemDevice extends AbstractItemRPCDevice {
///////////////////////////////////////////////////////////////////
private final Supplier<Optional<Location>> location;
private final Supplier<Optional<BlockLocation>> location;
private long gameTimeCooldownExpiresAt;
///////////////////////////////////////////////////////////////////
public SoundCardItemDevice(final ItemStack identity, final Supplier<Optional<Location>> location) {
public SoundCardItemDevice(final ItemStack identity, final Supplier<Optional<BlockLocation>> location) {
super(identity, "sound");
this.location = location;
}
@@ -43,8 +42,7 @@ public final class SoundCardItemDevice extends AbstractItemRPCDevice {
public void playSound(@Nullable @Parameter("name") final String name) {
if (name == null) throw new IllegalArgumentException();
location.get().ifPresent(location -> {
final LevelAccessor level = location.level();
location.get().ifPresent(location -> location.tryGetLevel().ifPresent(level -> {
if (!(level instanceof final ServerLevel serverLevel)) {
return;
}
@@ -58,8 +56,8 @@ public final class SoundCardItemDevice extends AbstractItemRPCDevice {
final SoundEvent soundEvent = ForgeRegistries.SOUND_EVENTS.getValue(new ResourceLocation(name));
if (soundEvent == null) throw new IllegalArgumentException("Sound not found.");
level.playSound(null, location.pos(), soundEvent, SoundSource.BLOCKS, 1, 1);
});
level.playSound(null, location.blockPos(), soundEvent, SoundSource.BLOCKS, 1, 1);
}));
}
@Callback

View File

@@ -3,7 +3,7 @@
package li.cil.oc2.common.bus.device.vm.item;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.util.Location;
import li.cil.oc2.common.util.BlockLocation;
import li.cil.oc2.common.util.SoundEvents;
import li.cil.oc2.common.util.ThrottledSoundEmitter;
import li.cil.sedna.device.block.ByteBufferBlockDevice;
@@ -23,7 +23,7 @@ public class HardDriveDevice extends AbstractBlockStorageDevice<ByteBufferBlockD
///////////////////////////////////////////////////////////////////
public HardDriveDevice(final ItemStack identity, final int size, final boolean readonly, final Supplier<Optional<Location>> location) {
public HardDriveDevice(final ItemStack identity, final int size, final boolean readonly, final Supplier<Optional<BlockLocation>> location) {
super(identity, readonly);
this.size = size;
this.soundEmitter = new ThrottledSoundEmitter(location, SoundEvents.HDD_ACCESS.get())

View File

@@ -3,7 +3,7 @@
package li.cil.oc2.common.bus.device.vm.item;
import com.google.common.io.ByteStreams;
import li.cil.oc2.common.util.Location;
import li.cil.oc2.common.util.BlockLocation;
import li.cil.sedna.api.device.BlockDevice;
import li.cil.sedna.device.block.ByteBufferBlockDevice;
import net.minecraft.world.item.ItemStack;
@@ -20,7 +20,7 @@ public final class HardDriveDeviceWithInitialData extends HardDriveDevice {
///////////////////////////////////////////////////////////////////
public HardDriveDeviceWithInitialData(final ItemStack identity, final BlockDevice base, final boolean readonly, final Supplier<Optional<Location>> location) {
public HardDriveDeviceWithInitialData(final ItemStack identity, final BlockDevice base, final boolean readonly, final Supplier<Optional<BlockLocation>> location) {
super(identity, (int) base.getCapacity(), readonly, location);
this.base = base;
}

View File

@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.Optional;
public record BlockLocation(WeakReference<LevelAccessor> level, BlockPos blockPos) {
public static Optional<BlockLocation> of(final Entity entity) {
if (entity.isAlive()) {
return Optional.of(new BlockLocation(new WeakReference<>(entity.level), entity.blockPosition()));
} else {
return Optional.empty();
}
}
public static Optional<BlockLocation> of(final BlockEntity blockEntity) {
if (!blockEntity.isRemoved()) {
return Optional.of(new BlockLocation(new WeakReference<>(blockEntity.getLevel()), blockEntity.getBlockPos()));
} else {
return Optional.empty();
}
}
public Optional<LevelAccessor> tryGetLevel() {
return Optional.ofNullable(level.get());
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof BlockLocation that) {
final LevelAccessor thisLevel = level.get();
final LevelAccessor thatLevel = that.level.get();
return Objects.equals(thisLevel, thatLevel) && Objects.equals(blockPos, that.blockPos);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(level.get(), blockPos);
}
}

View File

@@ -0,0 +1,40 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelAccessor;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.Optional;
public record ChunkLocation(WeakReference<LevelAccessor> level, ChunkPos position) {
public static ChunkLocation of(final LevelAccessor level, final BlockPos position) {
return new ChunkLocation(new WeakReference<>(level), new ChunkPos(position));
}
public static ChunkLocation of(final LevelAccessor level, final ChunkPos position) {
return new ChunkLocation(new WeakReference<>(level), position);
}
public Optional<LevelAccessor> tryGetLevel() {
return Optional.ofNullable(level.get());
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof ChunkLocation that) {
final LevelAccessor thisLevel = level.get();
final LevelAccessor thatLevel = that.level.get();
return Objects.equals(thisLevel, thatLevel) && Objects.equals(position, that.position);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(level.get(), position);
}
}

View File

@@ -1,29 +0,0 @@
/* SPDX-License-Identifier: MIT */
package li.cil.oc2.common.util;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.Objects;
import java.util.Optional;
public record Location(LevelAccessor level, BlockPos pos) {
public static Optional<Location> of(final Entity entity) {
if (entity.isAlive()) {
return Optional.of(new Location(entity.level, entity.blockPosition()));
} else {
return Optional.empty();
}
}
public static Optional<Location> of(final BlockEntity blockEntity) {
if (!blockEntity.isRemoved()) {
return Optional.of(new Location(Objects.requireNonNull(blockEntity.getLevel()), blockEntity.getBlockPos()));
} else {
return Optional.empty();
}
}
}

View File

@@ -7,32 +7,33 @@ import li.cil.oc2.api.bus.device.provider.ItemDeviceQuery;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.lang.ref.WeakReference;
import java.util.Optional;
import java.util.function.Supplier;
public final class LocationSupplierUtils {
public static Supplier<Optional<Location>> of(final BlockEntity blockEntity) {
return () -> Location.of(blockEntity);
public static Supplier<Optional<BlockLocation>> of(final BlockEntity blockEntity) {
return () -> BlockLocation.of(blockEntity);
}
public static Supplier<Optional<Location>> of(final Entity entity) {
return () -> Location.of(entity);
public static Supplier<Optional<BlockLocation>> of(final Entity entity) {
return () -> BlockLocation.of(entity);
}
public static Supplier<Optional<Location>> of(final BlockDeviceQuery query) {
final Optional<Location> location = Optional.of(new Location(query.getLevel(), query.getQueryPosition()));
public static Supplier<Optional<BlockLocation>> of(final BlockDeviceQuery query) {
final Optional<BlockLocation> location = Optional.of(new BlockLocation(new WeakReference<>(query.getLevel()), query.getQueryPosition()));
return () -> location;
}
public static Supplier<Optional<Location>> of(final ItemDeviceQuery query) {
public static Supplier<Optional<BlockLocation>> of(final ItemDeviceQuery query) {
final Optional<BlockEntity> blockEntity = query.getContainerBlockEntity();
if (blockEntity.isPresent()) {
return () -> Location.of(blockEntity.get());
return () -> BlockLocation.of(blockEntity.get());
}
final Optional<Entity> entity = query.getContainerEntity();
if (entity.isPresent()) {
return () -> Location.of(entity.get());
return () -> BlockLocation.of(entity.get());
}
return Optional::empty;

View File

@@ -45,7 +45,9 @@ public final class ServerScheduler {
}
public static void scheduleOnUnload(final LevelAccessor level, final Runnable listener) {
levelUnloadSchedulers.computeIfAbsent(level, unused -> new SimpleScheduler()).add(listener);
levelUnloadSchedulers
.computeIfAbsent(level, unused -> new SimpleScheduler())
.add(listener);
}
public static void cancelOnUnload(@Nullable final LevelAccessor level, final Runnable listener) {

View File

@@ -5,7 +5,6 @@ package li.cil.oc2.common.util;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.level.LevelAccessor;
import java.time.Duration;
import java.util.Optional;
@@ -13,7 +12,7 @@ import java.util.Random;
import java.util.function.Supplier;
public final class ThrottledSoundEmitter {
private final Supplier<Optional<Location>> location;
private final Supplier<Optional<BlockLocation>> location;
private final SoundEvent sound;
private long minInterval;
private SoundSource category;
@@ -26,7 +25,7 @@ public final class ThrottledSoundEmitter {
///////////////////////////////////////////////////////////////////
public ThrottledSoundEmitter(final Supplier<Optional<Location>> location, final SoundEvent sound) {
public ThrottledSoundEmitter(final Supplier<Optional<BlockLocation>> location, final SoundEvent sound) {
this.location = location;
this.sound = sound;
this.category = SoundSource.BLOCKS;
@@ -39,12 +38,11 @@ public final class ThrottledSoundEmitter {
final long now = System.currentTimeMillis();
if (now - lastEmittedTime > minInterval) {
lastEmittedTime = now;
this.location.get().ifPresent(location -> {
final LevelAccessor level = location.level();
this.location.get().ifPresent(location -> location.tryGetLevel().ifPresent(level -> {
final float volume = sampleVolume(level.getRandom());
final float pitch = samplePitch(level.getRandom());
LevelUtils.playSound(level, location.pos(), sound, category, volume, pitch);
});
LevelUtils.playSound(level, location.blockPos(), sound, category, volume, pitch);
}));
}
}