Merge remote-tracking branch 'origin/1.20' into 1.21.1

This commit is contained in:
embeddedt 2025-11-01 20:22:36 -04:00
commit eb6700eaf5
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 197 additions and 29 deletions

View File

@ -1,47 +1,29 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_sounds;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalNotification;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicSoundHelpers;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.Map;
@Mixin(SoundBufferLibrary.class)
@ClientOnlyMixin
public abstract class SoundBufferLibraryMixin {
private static final boolean debugDynamicSoundLoading = Boolean.getBoolean("modernfix.debugDynamicSoundLoading");
@Shadow @Final @Mutable
private Map<ResourceLocation, CompletableFuture<SoundBuffer>> cache = CacheBuilder.newBuilder()
.expireAfterAccess(DynamicSoundHelpers.MAX_SOUND_LIFETIME_SECS, TimeUnit.SECONDS)
.concurrencyLevel(1)
// Excessive use of type hinting due to it assuming Object as the broadest correct type
.<ResourceLocation, CompletableFuture<SoundBuffer>>removalListener(this::onSoundRemoval)
.build()
.asMap();
private Map<ResourceLocation, CompletableFuture<SoundBuffer>> cache;
private <K extends ResourceLocation, V extends CompletableFuture<SoundBuffer>> void onSoundRemoval(RemovalNotification<K, V> notification) {
if(notification.getCause() == RemovalCause.REPLACED && notification.getValue() == cache.get(notification.getKey()))
return;
notification.getValue().thenAccept(SoundBuffer::discardAlBuffer);
if(!debugDynamicSoundLoading)
return;
K k = notification.getKey();
if(k == null)
return;
ModernFix.LOGGER.warn("Evicted sound {}", k);
@Inject(method = "<init>", at = @At("RETURN"))
private void replaceCache(CallbackInfo ci) {
this.cache = new DynamicSoundHelpers.Cache(cache);
}
}

View File

@ -0,0 +1,37 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_sounds;
import com.mojang.blaze3d.audio.SoundBuffer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.dynamicresources.DynamicSoundHelpers;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.sound.sampled.AudioFormat;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
@ClientOnlyMixin
@Mixin(SoundBuffer.class)
public class SoundBufferMixin implements DynamicSoundHelpers.SoundBufAccess {
@Unique
private long mfix$durationNanos;
@Inject(method = "<init>", at = @At("RETURN"))
private void computeDuration(ByteBuffer data, AudioFormat format, CallbackInfo ci) {
if (data != null) {
int numFrames = data.capacity() / format.getFrameSize();
double seconds = ((double)numFrames / format.getFrameRate());
mfix$durationNanos = Math.max(0, (long)Math.ceil(seconds * 1_000_000_000.0));
} else {
mfix$durationNanos = 0;
}
}
@Override
public long mfix$getDurationNanos() {
return mfix$durationNanos;
}
}

View File

@ -1,8 +1,142 @@
package org.embeddedt.modernfix.dynamicresources;
import com.mojang.blaze3d.audio.SoundBuffer;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.NotNull;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class DynamicSoundHelpers {
/**
* The duration until a sound is eligible for eviction if unused.
*/
public static final int MAX_SOUND_LIFETIME_SECS = 300;
private static final long SOUND_EVICTION_DELAY = TimeUnit.SECONDS.toNanos(30);
private static final boolean debugDynamicSoundLoading = Boolean.getBoolean("modernfix.debugDynamicSoundLoading");
public interface SoundBufAccess {
long mfix$getDurationNanos();
}
public static final class Cache extends AbstractMap<ResourceLocation, CompletableFuture<SoundBuffer>> {
private static class Entry {
private final CompletableFuture<SoundBuffer> buffer;
private long lastAccessTime;
private Entry(CompletableFuture<SoundBuffer> buffer) {
this.buffer = buffer;
this.lastAccessTime = System.nanoTime();
}
public CompletableFuture<SoundBuffer> getBuffer() {
this.lastAccessTime = System.nanoTime();
return this.buffer;
}
public long getDuration() {
var buf = this.buffer.getNow(null);
if (buf == null) {
return 0;
}
return ((SoundBufAccess)buf).mfix$getDurationNanos();
}
public boolean isExpired(long currentTs) {
long duration = getDuration();
return duration > 0 && (currentTs - this.lastAccessTime) >= (duration + SOUND_EVICTION_DELAY);
}
public void discard() {
this.buffer.thenAccept(SoundBuffer::discardAlBuffer);
}
@Override
public String toString() {
return super.toString();
}
}
private final Object2ObjectLinkedOpenHashMap<ResourceLocation, Entry> store = new Object2ObjectLinkedOpenHashMap<>();
public Cache(Map<ResourceLocation, CompletableFuture<SoundBuffer>> otherMap) {
this.putAll(otherMap);
}
private void checkExpired() {
long ts = System.nanoTime();
var iter = this.store.object2ObjectEntrySet().fastIterator();
while (iter.hasNext()) {
var entry = iter.next();
if (entry.getValue().isExpired(ts)) {
if (debugDynamicSoundLoading) {
ModernFix.LOGGER.warn("Evicted sound {} with duration {} ms", entry.getKey(), entry.getValue().getDuration() / 1000000);
}
entry.getValue().discard();
iter.remove();
} else {
break;
}
}
}
@Override
public CompletableFuture<SoundBuffer> get(Object key) {
if (key instanceof ResourceLocation rl) {
var entry = this.store.getAndMoveToLast(rl);
CompletableFuture<SoundBuffer> result = entry != null ? entry.getBuffer() : null;
this.checkExpired();
return result;
} else {
return null;
}
}
@Override
public CompletableFuture<SoundBuffer> put(ResourceLocation key, CompletableFuture<SoundBuffer> value) {
var entry = new Entry(value);
if (debugDynamicSoundLoading) {
ModernFix.LOGGER.info("Loaded sound {}", key);
}
var previousEntry = this.store.putAndMoveToLast(key, entry);
return previousEntry != null ? previousEntry.getBuffer() : null;
}
@Override
public @NotNull Set<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> entrySet() {
return new EntrySet();
}
private class EntrySet extends AbstractSet<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> {
@Override
public Iterator<Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>>> iterator() {
var storeIter = store.entrySet().iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return storeIter.hasNext();
}
@Override
public Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>> next() {
var entry = storeIter.next();
return new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), entry.getValue().buffer);
}
};
}
@Override
public int size() {
return store.size();
}
@Override
public void clear() {
store.clear();
}
}
}
}

View File

@ -0,0 +1,15 @@
package org.embeddedt.modernfix.sound;
import com.mojang.blaze3d.audio.SoundBuffer;
import net.minecraft.resources.ResourceLocation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class SoundBufferCache extends LinkedHashMap<ResourceLocation, CompletableFuture<SoundBuffer>> {
@Override
protected boolean removeEldestEntry(Map.Entry<ResourceLocation, CompletableFuture<SoundBuffer>> eldest) {
return super.removeEldestEntry(eldest);
}
}