Allow for pony data to be loaded from off-thread processes

This commit is contained in:
Sollace 2021-11-25 00:41:12 +02:00
parent b3667ec3dc
commit 42cebcc1f6
5 changed files with 96 additions and 45 deletions

View file

@ -0,0 +1,43 @@
package com.minelittlepony.client.pony;
import java.util.function.Consumer;
public interface Memoize<T> {
T get();
default T get(T fallback) {
T value = get();
return value == null ? fallback : value;
}
default boolean isPresent() {
return true;
}
static <T> Memoize<T> of(T value) {
return () -> value;
}
static <T> Memoize<T> load(Consumer<Consumer<T>> factory) {
return new Memoize<>() {
T value;
boolean loadRequested;
@Override
public T get() {
synchronized (this) {
if (!loadRequested) {
loadRequested = true;
factory.accept(value -> {
this.value = value;
});
}
}
return value;
}
@Override
public boolean isPresent() {
return value != null;
}
};
}
}

View file

@ -37,11 +37,11 @@ import org.jetbrains.annotations.Unmodifiable;
public class Pony implements IPony { public class Pony implements IPony {
private final Identifier texture; private final Identifier texture;
private final IPonyData metadata; private final Memoize<IPonyData> metadata;
private boolean defaulted = false; private boolean defaulted = false;
Pony(Identifier resource, IPonyData data) { Pony(Identifier resource, Memoize<IPonyData> data) {
texture = resource; texture = resource;
metadata = data; metadata = data;
} }
@ -62,16 +62,20 @@ public class Pony implements IPony {
@Override @Override
public void updateForEntity(Entity entity) { public void updateForEntity(Entity entity) {
if (!metadata.isPresent()) {
return;
}
if (entity instanceof RegistrationHandler && ((RegistrationHandler)entity).shouldUpdateRegistration(this)) { if (entity instanceof RegistrationHandler && ((RegistrationHandler)entity).shouldUpdateRegistration(this)) {
entity.calculateDimensions(); entity.calculateDimensions();
PlayerEntity clientPlayer = MinecraftClient.getInstance().player; PlayerEntity clientPlayer = MinecraftClient.getInstance().player;
if (clientPlayer != null) { if (clientPlayer != null) {
if (Objects.equals(entity, clientPlayer) || Objects.equals(((PlayerEntity)entity).getGameProfile(), clientPlayer.getGameProfile())) { if (Objects.equals(entity, clientPlayer) || Objects.equals(((PlayerEntity)entity).getGameProfile(), clientPlayer.getGameProfile())) {
Channel.broadcastPonyData(new MsgPonyData(metadata, defaulted)); Channel.broadcastPonyData(new MsgPonyData(getMetadata(), defaulted));
} }
} }
PonyDataCallback.EVENT.invoker().onPonyDataAvailable((PlayerEntity)entity, metadata, defaulted, EnvType.CLIENT); PonyDataCallback.EVENT.invoker().onPonyDataAvailable((PlayerEntity)entity, getMetadata(), defaulted, EnvType.CLIENT);
} }
} }
@ -144,7 +148,7 @@ public class Pony implements IPony {
} }
protected Vec3d getVisualEyePosition(LivingEntity entity) { protected Vec3d getVisualEyePosition(LivingEntity entity) {
Size size = entity.isBaby() ? Sizes.FOAL : metadata.getSize(); Size size = entity.isBaby() ? Sizes.FOAL : getMetadata().getSize();
return new Vec3d( return new Vec3d(
entity.getX(), entity.getX(),
@ -155,7 +159,7 @@ public class Pony implements IPony {
@Override @Override
public Race getRace(boolean ignorePony) { public Race getRace(boolean ignorePony) {
return getEffectiveRace(metadata.getRace(), ignorePony); return getEffectiveRace(getMetadata().getRace(), ignorePony);
} }
@Override @Override
@ -165,15 +169,12 @@ public class Pony implements IPony {
@Override @Override
public IPonyData getMetadata() { public IPonyData getMetadata() {
return metadata; return metadata.get(PonyData.NULL);
} }
@Override @Override
public boolean isSitting(LivingEntity entity) { public boolean isSitting(LivingEntity entity) {
return entity.hasVehicle()/* return entity.hasVehicle();
|| (entity instanceof PlayerEntity
&& entity.getVelocity().x == 0 && entity.getVelocity().z == 0
&& !entity.isInsideWaterOrBubbleColumn() && entity.onGround && isCrouching(entity))*/;
} }
@Override @Override

View file

@ -34,21 +34,22 @@ public class PonyData implements IPonyData {
private static final PonyDataSerialiser SERIALISER = new PonyDataSerialiser(); private static final PonyDataSerialiser SERIALISER = new PonyDataSerialiser();
public static final IPonyData NULL = new PonyData(Race.HUMAN); public static final IPonyData NULL = new PonyData(Race.HUMAN);
public static final Memoize<IPonyData> MEM_NULL = Memoize.of(NULL);
/** /**
* Parses the given resource into a new IPonyData. * Parses the given resource into a new IPonyData.
* This may either come from an attached json file or the image itself. * This may either come from an attached json file or the image itself.
*/ */
public static IPonyData parse(@Nullable Identifier identifier) { public static Memoize<IPonyData> parse(@Nullable Identifier identifier) {
if (identifier == null) { if (identifier == null) {
return NULL; return MEM_NULL;
} }
try (Resource res = MinecraftClient.getInstance().getResourceManager().getResource(identifier)) { try (Resource res = MinecraftClient.getInstance().getResourceManager().getResource(identifier)) {
PonyData data = res.getMetadata(SERIALISER); PonyData data = res.getMetadata(SERIALISER);
if (data != null) { if (data != null) {
return data; return Memoize.of(data);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
// Ignore uploaded texture // Ignore uploaded texture
@ -56,12 +57,14 @@ public class PonyData implements IPonyData {
MineLittlePony.logger.warn("Unable to read {} metadata", identifier, e); MineLittlePony.logger.warn("Unable to read {} metadata", identifier, e);
} }
try { return Memoize.load(callback -> {
return NativeUtil.parseImage(identifier, NativePonyData::new); NativeUtil.parseImage(identifier, img -> {
} catch (IllegalStateException e) { callback.accept(new NativePonyData(img));
}, e -> {
MineLittlePony.logger.fatal("Unable to read {} metadata", identifier, e); MineLittlePony.logger.fatal("Unable to read {} metadata", identifier, e);
return NULL; callback.accept(NULL);
} });
});
} }
@Expose @Expose

View file

@ -49,7 +49,7 @@ public class PonyManager implements IPonyManager, IdentifiableResourceReloadList
try { try {
return poniesCache.get(resource); return poniesCache.get(resource);
} catch (ExecutionException e) { } catch (ExecutionException e) {
return new Pony(resource, PonyData.NULL); return new Pony(resource, Memoize.of(PonyData.NULL));
} }
} }

View file

@ -9,7 +9,7 @@ import com.mojang.blaze3d.systems.RenderSystem;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Consumer;
import static com.mojang.blaze3d.platform.GlStateManager._getTexLevelParameter; import static com.mojang.blaze3d.platform.GlStateManager._getTexLevelParameter;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
@ -91,14 +91,16 @@ public class NativeUtil {
} }
} }
public static <T> T parseImage(Identifier resource, Function<NativeImage, T> consumer) { public static void parseImage(Identifier resource, Consumer<NativeImage> consumer, Consumer<Exception> fail) {
try {
if (!RenderSystem.isOnRenderThread()) {
RenderSystem.recordRenderCall(() -> parseImage(resource, consumer, fail));
return;
}
MinecraftClient mc = MinecraftClient.getInstance(); MinecraftClient mc = MinecraftClient.getInstance();
TextureManager textures = mc.getTextureManager(); TextureManager textures = mc.getTextureManager();
if (!RenderSystem.isOnRenderThread()) {
throw new IllegalStateException("This can only be called from the main thread.");
}
// recreate NativeImage from the GL matrix // recreate NativeImage from the GL matrix
textures.bindTexture(resource); textures.bindTexture(resource);
@ -117,8 +119,10 @@ public class NativeUtil {
// This allocates a new array to store the image every time. // This allocates a new array to store the image every time.
// Don't do this every time. Keep a cache and store it so we don't destroy memory. // Don't do this every time. Keep a cache and store it so we don't destroy memory.
image.loadFromTextureImage(0, false); image.loadFromTextureImage(0, false);
consumer.accept(image);
return consumer.apply(image); }
} catch (Exception e) {
fail.accept(e);
} }
} }
} }