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 {
private final Identifier texture;
private final IPonyData metadata;
private final Memoize<IPonyData> metadata;
private boolean defaulted = false;
Pony(Identifier resource, IPonyData data) {
Pony(Identifier resource, Memoize<IPonyData> data) {
texture = resource;
metadata = data;
}
@ -62,16 +62,20 @@ public class Pony implements IPony {
@Override
public void updateForEntity(Entity entity) {
if (!metadata.isPresent()) {
return;
}
if (entity instanceof RegistrationHandler && ((RegistrationHandler)entity).shouldUpdateRegistration(this)) {
entity.calculateDimensions();
PlayerEntity clientPlayer = MinecraftClient.getInstance().player;
if (clientPlayer != null) {
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) {
Size size = entity.isBaby() ? Sizes.FOAL : metadata.getSize();
Size size = entity.isBaby() ? Sizes.FOAL : getMetadata().getSize();
return new Vec3d(
entity.getX(),
@ -155,7 +159,7 @@ public class Pony implements IPony {
@Override
public Race getRace(boolean ignorePony) {
return getEffectiveRace(metadata.getRace(), ignorePony);
return getEffectiveRace(getMetadata().getRace(), ignorePony);
}
@Override
@ -165,15 +169,12 @@ public class Pony implements IPony {
@Override
public IPonyData getMetadata() {
return metadata;
return metadata.get(PonyData.NULL);
}
@Override
public boolean isSitting(LivingEntity entity) {
return entity.hasVehicle()/*
|| (entity instanceof PlayerEntity
&& entity.getVelocity().x == 0 && entity.getVelocity().z == 0
&& !entity.isInsideWaterOrBubbleColumn() && entity.onGround && isCrouching(entity))*/;
return entity.hasVehicle();
}
@Override

View file

@ -34,21 +34,22 @@ public class PonyData implements IPonyData {
private static final PonyDataSerialiser SERIALISER = new PonyDataSerialiser();
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.
* 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) {
return NULL;
return MEM_NULL;
}
try (Resource res = MinecraftClient.getInstance().getResourceManager().getResource(identifier)) {
PonyData data = res.getMetadata(SERIALISER);
if (data != null) {
return data;
return Memoize.of(data);
}
} catch (FileNotFoundException e) {
// Ignore uploaded texture
@ -56,12 +57,14 @@ public class PonyData implements IPonyData {
MineLittlePony.logger.warn("Unable to read {} metadata", identifier, e);
}
try {
return NativeUtil.parseImage(identifier, NativePonyData::new);
} catch (IllegalStateException e) {
MineLittlePony.logger.fatal("Unable to read {} metadata", identifier, e);
return NULL;
}
return Memoize.load(callback -> {
NativeUtil.parseImage(identifier, img -> {
callback.accept(new NativePonyData(img));
}, e -> {
MineLittlePony.logger.fatal("Unable to read {} metadata", identifier, e);
callback.accept(NULL);
});
});
}
@Expose

View file

@ -49,7 +49,7 @@ public class PonyManager implements IPonyManager, IdentifiableResourceReloadList
try {
return poniesCache.get(resource);
} 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.Map;
import java.util.function.Function;
import java.util.function.Consumer;
import static com.mojang.blaze3d.platform.GlStateManager._getTexLevelParameter;
import static org.lwjgl.opengl.GL11.*;
@ -91,34 +91,38 @@ public class NativeUtil {
}
}
public static <T> T parseImage(Identifier resource, Function<NativeImage, T> consumer) {
MinecraftClient mc = MinecraftClient.getInstance();
TextureManager textures = mc.getTextureManager();
public static void parseImage(Identifier resource, Consumer<NativeImage> consumer, Consumer<Exception> fail) {
try {
if (!RenderSystem.isOnRenderThread()) {
RenderSystem.recordRenderCall(() -> parseImage(resource, consumer, fail));
return;
}
if (!RenderSystem.isOnRenderThread()) {
throw new IllegalStateException("This can only be called from the main thread.");
}
MinecraftClient mc = MinecraftClient.getInstance();
TextureManager textures = mc.getTextureManager();
// recreate NativeImage from the GL matrix
textures.bindTexture(resource);
// recreate NativeImage from the GL matrix
textures.bindTexture(resource);
// TODO: This returns values that are too specific.
// Can we change the level (0) here to something
// else to actually get what we need?
int format = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT);
int width = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH);
int height = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT);
// TODO: This returns values that are too specific.
// Can we change the level (0) here to something
// else to actually get what we need?
int format = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT);
int width = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH);
int height = _getTexLevelParameter(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT);
if (width * height == 0) {
throw new IllegalStateException("GL texture not uploaded yet");
}
if (width * height == 0) {
throw new IllegalStateException("GL texture not uploaded yet");
}
try (NativeImage image = new NativeImage(InternalFormat.valueOf(format).getClassification(), width, height, false)) {
// 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.
image.loadFromTextureImage(0, false);
return consumer.apply(image);
try (NativeImage image = new NativeImage(InternalFormat.valueOf(format).getClassification(), width, height, false)) {
// 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.
image.loadFromTextureImage(0, false);
consumer.accept(image);
}
} catch (Exception e) {
fail.accept(e);
}
}
}