365 lines
17 KiB
Java
365 lines
17 KiB
Java
|
package net.minecraft.client.gui.font;
|
||
|
|
||
|
import com.google.common.collect.Lists;
|
||
|
import com.google.gson.Gson;
|
||
|
import com.google.gson.GsonBuilder;
|
||
|
import com.google.gson.JsonElement;
|
||
|
import com.google.gson.JsonParseException;
|
||
|
import com.mojang.blaze3d.font.GlyphProvider;
|
||
|
import com.mojang.datafixers.util.Either;
|
||
|
import com.mojang.datafixers.util.Pair;
|
||
|
import com.mojang.logging.LogUtils;
|
||
|
import com.mojang.serialization.Codec;
|
||
|
import com.mojang.serialization.JsonOps;
|
||
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||
|
import com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
|
||
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||
|
import java.io.Reader;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.EnumSet;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
import java.util.Optional;
|
||
|
import java.util.Set;
|
||
|
import java.util.Map.Entry;
|
||
|
import java.util.concurrent.CompletableFuture;
|
||
|
import java.util.concurrent.Executor;
|
||
|
import java.util.function.Consumer;
|
||
|
import java.util.function.Function;
|
||
|
import java.util.stream.Stream;
|
||
|
import javax.annotation.Nullable;
|
||
|
import net.minecraft.Util;
|
||
|
import net.minecraft.client.Minecraft;
|
||
|
import net.minecraft.client.Options;
|
||
|
import net.minecraft.client.gui.Font;
|
||
|
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition;
|
||
|
import net.minecraft.client.renderer.texture.TextureManager;
|
||
|
import net.minecraft.resources.FileToIdConverter;
|
||
|
import net.minecraft.resources.ResourceLocation;
|
||
|
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||
|
import net.minecraft.server.packs.resources.Resource;
|
||
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||
|
import net.minecraft.util.DependencySorter;
|
||
|
import net.minecraft.util.profiling.Profiler;
|
||
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||
|
import net.minecraftforge.api.distmarker.Dist;
|
||
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||
|
import org.slf4j.Logger;
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
public class FontManager implements PreparableReloadListener, AutoCloseable {
|
||
|
static final Logger LOGGER = LogUtils.getLogger();
|
||
|
private static final String FONTS_PATH = "fonts.json";
|
||
|
public static final ResourceLocation MISSING_FONT = ResourceLocation.withDefaultNamespace("missing");
|
||
|
private static final FileToIdConverter FONT_DEFINITIONS = FileToIdConverter.json("font");
|
||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
|
||
|
private final FontSet missingFontSet;
|
||
|
private final List<GlyphProvider> providersToClose = new ArrayList<>();
|
||
|
private final Map<ResourceLocation, FontSet> fontSets = new HashMap<>();
|
||
|
private final TextureManager textureManager;
|
||
|
@Nullable
|
||
|
private volatile FontSet lastFontSetCache;
|
||
|
|
||
|
public FontManager(TextureManager p_95005_) {
|
||
|
this.textureManager = p_95005_;
|
||
|
this.missingFontSet = Util.make(new FontSet(p_95005_, MISSING_FONT), p_325351_ -> p_325351_.reload(List.of(createFallbackProvider()), Set.of()));
|
||
|
}
|
||
|
|
||
|
private static GlyphProvider.Conditional createFallbackProvider() {
|
||
|
return new GlyphProvider.Conditional(new AllMissingGlyphProvider(), FontOption.Filter.ALWAYS_PASS);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public CompletableFuture<Void> reload(
|
||
|
PreparableReloadListener.PreparationBarrier p_285160_, ResourceManager p_285231_, Executor p_284975_, Executor p_285218_
|
||
|
) {
|
||
|
return this.prepare(p_285231_, p_284975_)
|
||
|
.thenCompose(p_285160_::wait)
|
||
|
.thenAcceptAsync(p_357671_ -> this.apply(p_357671_, Profiler.get()), p_285218_);
|
||
|
}
|
||
|
|
||
|
private CompletableFuture<FontManager.Preparation> prepare(ResourceManager p_285252_, Executor p_284969_) {
|
||
|
List<CompletableFuture<FontManager.UnresolvedBuilderBundle>> list = new ArrayList<>();
|
||
|
|
||
|
for (Entry<ResourceLocation, List<Resource>> entry : FONT_DEFINITIONS.listMatchingResourceStacks(p_285252_).entrySet()) {
|
||
|
ResourceLocation resourcelocation = FONT_DEFINITIONS.fileToId(entry.getKey());
|
||
|
list.add(CompletableFuture.supplyAsync(() -> {
|
||
|
List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> list1 = loadResourceStack(entry.getValue(), resourcelocation);
|
||
|
FontManager.UnresolvedBuilderBundle fontmanager$unresolvedbuilderbundle = new FontManager.UnresolvedBuilderBundle(resourcelocation);
|
||
|
|
||
|
for (Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional> pair : list1) {
|
||
|
FontManager.BuilderId fontmanager$builderid = pair.getFirst();
|
||
|
FontOption.Filter fontoption$filter = pair.getSecond().filter();
|
||
|
pair.getSecond().definition().unpack().ifLeft(p_325337_ -> {
|
||
|
CompletableFuture<Optional<GlyphProvider>> completablefuture = this.safeLoad(fontmanager$builderid, p_325337_, p_285252_, p_284969_);
|
||
|
fontmanager$unresolvedbuilderbundle.add(fontmanager$builderid, fontoption$filter, completablefuture);
|
||
|
}).ifRight(p_325345_ -> fontmanager$unresolvedbuilderbundle.add(fontmanager$builderid, fontoption$filter, p_325345_));
|
||
|
}
|
||
|
|
||
|
return fontmanager$unresolvedbuilderbundle;
|
||
|
}, p_284969_));
|
||
|
}
|
||
|
|
||
|
return Util.sequence(list)
|
||
|
.thenCompose(
|
||
|
p_325341_ -> {
|
||
|
List<CompletableFuture<Optional<GlyphProvider>>> list1 = p_325341_.stream()
|
||
|
.flatMap(FontManager.UnresolvedBuilderBundle::listBuilders)
|
||
|
.collect(Util.toMutableList());
|
||
|
GlyphProvider.Conditional glyphprovider$conditional = createFallbackProvider();
|
||
|
list1.add(CompletableFuture.completedFuture(Optional.of(glyphprovider$conditional.provider())));
|
||
|
return Util.sequence(list1)
|
||
|
.thenCompose(
|
||
|
p_284618_ -> {
|
||
|
Map<ResourceLocation, List<GlyphProvider.Conditional>> map = this.resolveProviders(p_325341_);
|
||
|
CompletableFuture<?>[] completablefuture = map.values()
|
||
|
.stream()
|
||
|
.map(p_284585_ -> CompletableFuture.runAsync(() -> this.finalizeProviderLoading(p_284585_, glyphprovider$conditional), p_284969_))
|
||
|
.toArray(CompletableFuture[]::new);
|
||
|
return CompletableFuture.allOf(completablefuture).thenApply(p_284595_ -> {
|
||
|
List<GlyphProvider> list2 = p_284618_.stream().flatMap(Optional::stream).toList();
|
||
|
return new FontManager.Preparation(map, list2);
|
||
|
});
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private CompletableFuture<Optional<GlyphProvider>> safeLoad(
|
||
|
FontManager.BuilderId p_285113_, GlyphProviderDefinition.Loader p_286561_, ResourceManager p_285424_, Executor p_285371_
|
||
|
) {
|
||
|
return CompletableFuture.supplyAsync(() -> {
|
||
|
try {
|
||
|
return Optional.of(p_286561_.load(p_285424_));
|
||
|
} catch (Exception exception) {
|
||
|
LOGGER.warn("Failed to load builder {}, rejecting", p_285113_, exception);
|
||
|
return Optional.empty();
|
||
|
}
|
||
|
}, p_285371_);
|
||
|
}
|
||
|
|
||
|
private Map<ResourceLocation, List<GlyphProvider.Conditional>> resolveProviders(List<FontManager.UnresolvedBuilderBundle> p_285282_) {
|
||
|
Map<ResourceLocation, List<GlyphProvider.Conditional>> map = new HashMap<>();
|
||
|
DependencySorter<ResourceLocation, FontManager.UnresolvedBuilderBundle> dependencysorter = new DependencySorter<>();
|
||
|
p_285282_.forEach(p_284626_ -> dependencysorter.addEntry(p_284626_.fontId, p_284626_));
|
||
|
dependencysorter.orderByDependencies(
|
||
|
(p_284620_, p_284621_) -> p_284621_.resolve(map::get).ifPresent(p_284590_ -> map.put(p_284620_, (List<GlyphProvider.Conditional>)p_284590_))
|
||
|
);
|
||
|
return map;
|
||
|
}
|
||
|
|
||
|
private void finalizeProviderLoading(List<GlyphProvider.Conditional> p_285520_, GlyphProvider.Conditional p_328834_) {
|
||
|
p_285520_.add(0, p_328834_);
|
||
|
IntSet intset = new IntOpenHashSet();
|
||
|
|
||
|
for (GlyphProvider.Conditional glyphprovider$conditional : p_285520_) {
|
||
|
intset.addAll(glyphprovider$conditional.provider().getSupportedGlyphs());
|
||
|
}
|
||
|
|
||
|
intset.forEach(p_325339_ -> {
|
||
|
if (p_325339_ != 32) {
|
||
|
for (GlyphProvider.Conditional glyphprovider$conditional1 : Lists.reverse(p_285520_)) {
|
||
|
if (glyphprovider$conditional1.provider().getGlyph(p_325339_) != null) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private static Set<FontOption> getFontOptions(Options p_331588_) {
|
||
|
Set<FontOption> set = EnumSet.noneOf(FontOption.class);
|
||
|
if (p_331588_.forceUnicodeFont().get()) {
|
||
|
set.add(FontOption.UNIFORM);
|
||
|
}
|
||
|
|
||
|
if (p_331588_.japaneseGlyphVariants().get()) {
|
||
|
set.add(FontOption.JAPANESE_VARIANTS);
|
||
|
}
|
||
|
|
||
|
return set;
|
||
|
}
|
||
|
|
||
|
private void apply(FontManager.Preparation p_284939_, ProfilerFiller p_285407_) {
|
||
|
p_285407_.push("closing");
|
||
|
this.lastFontSetCache = null;
|
||
|
this.fontSets.values().forEach(FontSet::close);
|
||
|
this.fontSets.clear();
|
||
|
this.providersToClose.forEach(GlyphProvider::close);
|
||
|
this.providersToClose.clear();
|
||
|
Set<FontOption> set = getFontOptions(Minecraft.getInstance().options);
|
||
|
p_285407_.popPush("reloading");
|
||
|
p_284939_.fontSets().forEach((p_325349_, p_325350_) -> {
|
||
|
FontSet fontset = new FontSet(this.textureManager, p_325349_);
|
||
|
fontset.reload(Lists.reverse((List<GlyphProvider.Conditional>)p_325350_), set);
|
||
|
this.fontSets.put(p_325349_, fontset);
|
||
|
});
|
||
|
this.providersToClose.addAll(p_284939_.allProviders);
|
||
|
p_285407_.pop();
|
||
|
if (!this.fontSets.containsKey(Minecraft.DEFAULT_FONT)) {
|
||
|
throw new IllegalStateException("Default font failed to load");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void updateOptions(Options p_335215_) {
|
||
|
Set<FontOption> set = getFontOptions(p_335215_);
|
||
|
|
||
|
for (FontSet fontset : this.fontSets.values()) {
|
||
|
fontset.reload(set);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> loadResourceStack(List<Resource> p_284976_, ResourceLocation p_285272_) {
|
||
|
List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> list = new ArrayList<>();
|
||
|
|
||
|
for (Resource resource : p_284976_) {
|
||
|
try (Reader reader = resource.openAsReader()) {
|
||
|
JsonElement jsonelement = GSON.fromJson(reader, JsonElement.class);
|
||
|
FontManager.FontDefinitionFile fontmanager$fontdefinitionfile = FontManager.FontDefinitionFile.CODEC
|
||
|
.parse(JsonOps.INSTANCE, jsonelement)
|
||
|
.getOrThrow(JsonParseException::new);
|
||
|
List<GlyphProviderDefinition.Conditional> list1 = fontmanager$fontdefinitionfile.providers;
|
||
|
|
||
|
for (int i = list1.size() - 1; i >= 0; i--) {
|
||
|
FontManager.BuilderId fontmanager$builderid = new FontManager.BuilderId(p_285272_, resource.sourcePackId(), i);
|
||
|
list.add(Pair.of(fontmanager$builderid, list1.get(i)));
|
||
|
}
|
||
|
} catch (Exception exception) {
|
||
|
LOGGER.warn("Unable to load font '{}' in {} in resourcepack: '{}'", p_285272_, "fonts.json", resource.sourcePackId(), exception);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
public Font createFont() {
|
||
|
return new Font(this::getFontSetCached, false);
|
||
|
}
|
||
|
|
||
|
public Font createFontFilterFishy() {
|
||
|
return new Font(this::getFontSetCached, true);
|
||
|
}
|
||
|
|
||
|
private FontSet getFontSetRaw(ResourceLocation p_333296_) {
|
||
|
return this.fontSets.getOrDefault(p_333296_, this.missingFontSet);
|
||
|
}
|
||
|
|
||
|
private FontSet getFontSetCached(ResourceLocation p_332431_) {
|
||
|
FontSet fontset = this.lastFontSetCache;
|
||
|
if (fontset != null && p_332431_.equals(fontset.name())) {
|
||
|
return fontset;
|
||
|
} else {
|
||
|
FontSet fontset1 = this.getFontSetRaw(p_332431_);
|
||
|
this.lastFontSetCache = fontset1;
|
||
|
return fontset1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() {
|
||
|
this.fontSets.values().forEach(FontSet::close);
|
||
|
this.providersToClose.forEach(GlyphProvider::close);
|
||
|
this.missingFontSet.close();
|
||
|
}
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
record BuilderId(ResourceLocation fontId, String pack, int index) {
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "(" + this.fontId + ": builder #" + this.index + " from pack " + this.pack + ")";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
record BuilderResult(
|
||
|
FontManager.BuilderId id, FontOption.Filter filter, Either<CompletableFuture<Optional<GlyphProvider>>, ResourceLocation> result
|
||
|
) {
|
||
|
public Optional<List<GlyphProvider.Conditional>> resolve(Function<ResourceLocation, List<GlyphProvider.Conditional>> p_284942_) {
|
||
|
return this.result
|
||
|
.map(
|
||
|
p_325356_ -> p_325356_.join().map(p_325357_ -> List.of(new GlyphProvider.Conditional(p_325357_, this.filter))),
|
||
|
p_325359_ -> {
|
||
|
List<GlyphProvider.Conditional> list = p_284942_.apply(p_325359_);
|
||
|
if (list == null) {
|
||
|
FontManager.LOGGER
|
||
|
.warn(
|
||
|
"Can't find font {} referenced by builder {}, either because it's missing, failed to load or is part of loading cycle",
|
||
|
p_325359_,
|
||
|
this.id
|
||
|
);
|
||
|
return Optional.empty();
|
||
|
} else {
|
||
|
return Optional.of(list.stream().map(this::mergeFilters).toList());
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
private GlyphProvider.Conditional mergeFilters(GlyphProvider.Conditional p_330532_) {
|
||
|
return new GlyphProvider.Conditional(p_330532_.provider(), this.filter.merge(p_330532_.filter()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
record FontDefinitionFile(List<GlyphProviderDefinition.Conditional> providers) {
|
||
|
public static final Codec<FontManager.FontDefinitionFile> CODEC = RecordCodecBuilder.create(
|
||
|
p_325360_ -> p_325360_.group(
|
||
|
GlyphProviderDefinition.Conditional.CODEC.listOf().fieldOf("providers").forGetter(FontManager.FontDefinitionFile::providers)
|
||
|
)
|
||
|
.apply(p_325360_, FontManager.FontDefinitionFile::new)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
record Preparation(Map<ResourceLocation, List<GlyphProvider.Conditional>> fontSets, List<GlyphProvider> allProviders) {
|
||
|
}
|
||
|
|
||
|
@OnlyIn(Dist.CLIENT)
|
||
|
record UnresolvedBuilderBundle(ResourceLocation fontId, List<FontManager.BuilderResult> builders, Set<ResourceLocation> dependencies)
|
||
|
implements DependencySorter.Entry<ResourceLocation> {
|
||
|
public UnresolvedBuilderBundle(ResourceLocation p_284984_) {
|
||
|
this(p_284984_, new ArrayList<>(), new HashSet<>());
|
||
|
}
|
||
|
|
||
|
public void add(FontManager.BuilderId p_284935_, FontOption.Filter p_336303_, GlyphProviderDefinition.Reference p_334249_) {
|
||
|
this.builders.add(new FontManager.BuilderResult(p_284935_, p_336303_, Either.right(p_334249_.id())));
|
||
|
this.dependencies.add(p_334249_.id());
|
||
|
}
|
||
|
|
||
|
public void add(FontManager.BuilderId p_286837_, FontOption.Filter p_334374_, CompletableFuture<Optional<GlyphProvider>> p_331945_) {
|
||
|
this.builders.add(new FontManager.BuilderResult(p_286837_, p_334374_, Either.left(p_331945_)));
|
||
|
}
|
||
|
|
||
|
private Stream<CompletableFuture<Optional<GlyphProvider>>> listBuilders() {
|
||
|
return this.builders.stream().flatMap(p_285041_ -> p_285041_.result.left().stream());
|
||
|
}
|
||
|
|
||
|
public Optional<List<GlyphProvider.Conditional>> resolve(Function<ResourceLocation, List<GlyphProvider.Conditional>> p_285118_) {
|
||
|
List<GlyphProvider.Conditional> list = new ArrayList<>();
|
||
|
|
||
|
for (FontManager.BuilderResult fontmanager$builderresult : this.builders) {
|
||
|
Optional<List<GlyphProvider.Conditional>> optional = fontmanager$builderresult.resolve(p_285118_);
|
||
|
if (!optional.isPresent()) {
|
||
|
return Optional.empty();
|
||
|
}
|
||
|
|
||
|
list.addAll(optional.get());
|
||
|
}
|
||
|
|
||
|
return Optional.of(list);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void visitRequiredDependencies(Consumer<ResourceLocation> p_285391_) {
|
||
|
this.dependencies.forEach(p_285391_);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void visitOptionalDependencies(Consumer<ResourceLocation> p_285405_) {
|
||
|
}
|
||
|
}
|
||
|
}
|