FontManager.java

net.minecraft.client.gui.font.FontManager

信息

  • 全限定名:net.minecraft.client.gui.font.FontManager
  • 类型:public class
  • 包:net.minecraft.client.gui.font
  • 源码路径:src/main/java/net/minecraft/client/gui/font/FontManager.java
  • 起始行号:L58
  • 实现:AutoCloseable, PreparableReloadListener
  • 职责:

    TODO

字段/常量

  • LOGGER

    • 类型: Logger
    • 修饰符: private static final
    • 源码定位: L59
    • 说明:

      TODO

  • FONTS_PATH

    • 类型: String
    • 修饰符: private static final
    • 源码定位: L60
    • 说明:

      TODO

  • MISSING_FONT

    • 类型: Identifier
    • 修饰符: public static final
    • 源码定位: L61
    • 说明:

      TODO

  • FONT_DEFINITIONS

    • 类型: FileToIdConverter
    • 修饰符: private static final
    • 源码定位: L62
    • 说明:

      TODO

  • GSON

    • 类型: Gson
    • 修饰符: private static final
    • 源码定位: L63
    • 说明:

      TODO

  • missingFontSet

    • 类型: FontSet
    • 修饰符: private final
    • 源码定位: L64
    • 说明:

      TODO

  • providersToClose

    • 类型: List<GlyphProvider>
    • 修饰符: private final
    • 源码定位: L65
    • 说明:

      TODO

  • fontSets

    • 类型: Map<Identifier,FontSet>
    • 修饰符: private final
    • 源码定位: L66
    • 说明:

      TODO

  • textureManager

    • 类型: TextureManager
    • 修饰符: private final
    • 源码定位: L67
    • 说明:

      TODO

  • anyGlyphs

    • 类型: FontManager.CachedFontProvider
    • 修饰符: private final
    • 源码定位: L68
    • 说明:

      TODO

  • nonFishyGlyphs

    • 类型: FontManager.CachedFontProvider
    • 修饰符: private final
    • 源码定位: L69
    • 说明:

      TODO

  • atlasManager

    • 类型: AtlasManager
    • 修饰符: private final
    • 源码定位: L70
    • 说明:

      TODO

  • atlasProviders

    • 类型: Map<Identifier,AtlasGlyphProvider>
    • 修饰符: private final
    • 源码定位: L71
    • 说明:

      TODO

  • playerProvider

    • 类型: PlayerGlyphProvider
    • 修饰符: private final
    • 源码定位: L72
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.font.FontManager.BuilderId

    • 类型: record
    • 修饰符: private
    • 源码定位: L287
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.BuilderResult

    • 类型: record
    • 修饰符: private
    • 源码定位: L295
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.CachedFontProvider

    • 类型: class
    • 修饰符: private
    • 源码定位: L323
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.CachedFontProvider.CachedEntry

    • 类型: record
    • 修饰符: private
    • 源码定位: L377
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.FontDefinitionFile

    • 类型: record
    • 修饰符: private
    • 源码定位: L382
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.Preparation

    • 类型: record
    • 修饰符: private
    • 源码定位: L390
    • 说明:

      TODO

  • net.minecraft.client.gui.font.FontManager.UnresolvedBuilderBundle

    • 类型: record
    • 修饰符: private
    • 源码定位: L394
    • 说明:

      TODO

构造器

public FontManager(TextureManager textureManager, AtlasManager atlasManager, PlayerSkinRenderCache playerSkinRenderCache) @ L74

  • 构造器名:FontManager
  • 源码定位:L74
  • 修饰符:public

参数:

  • textureManager: TextureManager
  • atlasManager: AtlasManager
  • playerSkinRenderCache: PlayerSkinRenderCache

说明:

TODO

方法

下面的方法块按源码顺序生成。

private FontSet createFontSet(Identifier id, List<GlyphProvider.Conditional> providers, Set<FontOption> options) @ L81

  • 方法名:createFontSet
  • 源码定位:L81
  • 返回类型:FontSet
  • 修饰符:private

参数:

  • id: Identifier
  • providers: List<GlyphProvider.Conditional>
  • options: Set

说明:

TODO

private static GlyphProvider.Conditional createFallbackProvider() @ L88

  • 方法名:createFallbackProvider
  • 源码定位:L88
  • 返回类型:GlyphProvider.Conditional
  • 修饰符:private static

参数:

说明:

TODO

public CompletableFuture<Void> reload(PreparableReloadListener.SharedState currentReload, Executor taskExecutor, PreparableReloadListener.PreparationBarrier preparationBarrier, Executor reloadExecutor) @ L92

  • 方法名:reload
  • 源码定位:L92
  • 返回类型:CompletableFuture
  • 修饰符:public

参数:

  • currentReload: PreparableReloadListener.SharedState
  • taskExecutor: Executor
  • preparationBarrier: PreparableReloadListener.PreparationBarrier
  • reloadExecutor: Executor

说明:

TODO

private CompletableFuture<FontManager.Preparation> prepare(ResourceManager manager, Executor executor) @ L104

  • 方法名:prepare
  • 源码定位:L104
  • 返回类型:CompletableFuture<FontManager.Preparation>
  • 修饰符:private

参数:

  • manager: ResourceManager
  • executor: Executor

说明:

TODO

private CompletableFuture<Optional<GlyphProvider>> safeLoad(FontManager.BuilderId id, GlyphProviderDefinition.Loader provider, ResourceManager manager, Executor executor) @ L152

  • 方法名:safeLoad
  • 源码定位:L152
  • 返回类型:CompletableFuture<Optional>
  • 修饰符:private

参数:

  • id: FontManager.BuilderId
  • provider: GlyphProviderDefinition.Loader
  • manager: ResourceManager
  • executor: Executor

说明:

TODO

private Map<Identifier,List<GlyphProvider.Conditional>> resolveProviders(List<FontManager.UnresolvedBuilderBundle> unresolvedProviders) @ L165

  • 方法名:resolveProviders
  • 源码定位:L165
  • 返回类型:Map<Identifier,List<GlyphProvider.Conditional>>
  • 修饰符:private

参数:

  • unresolvedProviders: List<FontManager.UnresolvedBuilderBundle>

说明:

TODO

private void finalizeProviderLoading(List<GlyphProvider.Conditional> list, GlyphProvider.Conditional fallback) @ L173

  • 方法名:finalizeProviderLoading
  • 源码定位:L173
  • 返回类型:void
  • 修饰符:private

参数:

  • list: List<GlyphProvider.Conditional>
  • fallback: GlyphProvider.Conditional

说明:

TODO

private static Set<FontOption> getFontOptions(Options options) @ L192

  • 方法名:getFontOptions
  • 源码定位:L192
  • 返回类型:Set
  • 修饰符:private static

参数:

  • options: Options

说明:

TODO

private void apply(FontManager.Preparation preparations, ProfilerFiller profiler) @ L205

  • 方法名:apply
  • 源码定位:L205
  • 返回类型:void
  • 修饰符:private

参数:

  • preparations: FontManager.Preparation
  • profiler: ProfilerFiller

说明:

TODO

public void updateOptions(Options options) @ L229

  • 方法名:updateOptions
  • 源码定位:L229
  • 返回类型:void
  • 修饰符:public

参数:

  • options: Options

说明:

TODO

private static List<Pair<FontManager.BuilderId,GlyphProviderDefinition.Conditional>> loadResourceStack(List<Resource> resourceStack, Identifier fontName) @ L237

  • 方法名:loadResourceStack
  • 源码定位:L237
  • 返回类型:List<Pair<FontManager.BuilderId,GlyphProviderDefinition.Conditional>>
  • 修饰符:private static

参数:

  • resourceStack: List
  • fontName: Identifier

说明:

TODO

public Font createFont() @ L260

  • 方法名:createFont
  • 源码定位:L260
  • 返回类型:Font
  • 修饰符:public

参数:

说明:

TODO

public Font createFontFilterFishy() @ L264

  • 方法名:createFontFilterFishy
  • 源码定位:L264
  • 返回类型:Font
  • 修饰符:public

参数:

说明:

TODO

private FontSet getFontSetRaw(Identifier id) @ L268

  • 方法名:getFontSetRaw
  • 源码定位:L268
  • 返回类型:FontSet
  • 修饰符:private

参数:

  • id: Identifier

说明:

TODO

private GlyphSource getSpriteFont(FontDescription.AtlasSprite contents) @ L272

  • 方法名:getSpriteFont
  • 源码定位:L272
  • 返回类型:GlyphSource
  • 修饰符:private

参数:

  • contents: FontDescription.AtlasSprite

说明:

TODO

public void close() @ L277

  • 方法名:close
  • 源码定位:L277
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class FontManager implements AutoCloseable, PreparableReloadListener {
    private static final Logger LOGGER = LogUtils.getLogger();
    private static final String FONTS_PATH = "fonts.json";
    public static final Identifier MISSING_FONT = Identifier.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<Identifier, FontSet> fontSets = new HashMap<>();
    private final TextureManager textureManager;
    private final FontManager.CachedFontProvider anyGlyphs = new FontManager.CachedFontProvider(false);
    private final FontManager.CachedFontProvider nonFishyGlyphs = new FontManager.CachedFontProvider(true);
    private final AtlasManager atlasManager;
    private final Map<Identifier, AtlasGlyphProvider> atlasProviders = new HashMap<>();
    private final PlayerGlyphProvider playerProvider;
 
    public FontManager(TextureManager textureManager, AtlasManager atlasManager, PlayerSkinRenderCache playerSkinRenderCache) {
        this.textureManager = textureManager;
        this.atlasManager = atlasManager;
        this.missingFontSet = this.createFontSet(MISSING_FONT, List.of(createFallbackProvider()), Set.of());
        this.playerProvider = new PlayerGlyphProvider(playerSkinRenderCache);
    }
 
    private FontSet createFontSet(Identifier id, List<GlyphProvider.Conditional> providers, Set<FontOption> options) {
        GlyphStitcher stitcher = new GlyphStitcher(this.textureManager, id);
        FontSet result = new FontSet(stitcher);
        result.reload(providers, options);
        return result;
    }
 
    private static GlyphProvider.Conditional createFallbackProvider() {
        return new GlyphProvider.Conditional(new AllMissingGlyphProvider(), FontOption.Filter.ALWAYS_PASS);
    }
 
    @Override
    public CompletableFuture<Void> reload(
        PreparableReloadListener.SharedState currentReload,
        Executor taskExecutor,
        PreparableReloadListener.PreparationBarrier preparationBarrier,
        Executor reloadExecutor
    ) {
        return this.prepare(currentReload.resourceManager(), taskExecutor)
            .thenCompose(preparationBarrier::wait)
            .thenAcceptAsync(preparations -> this.apply(preparations, Profiler.get()), reloadExecutor);
    }
 
    private CompletableFuture<FontManager.Preparation> prepare(ResourceManager manager, Executor executor) {
        List<CompletableFuture<FontManager.UnresolvedBuilderBundle>> builderFutures = new ArrayList<>();
 
        for (Entry<Identifier, List<Resource>> fontStack : FONT_DEFINITIONS.listMatchingResourceStacks(manager).entrySet()) {
            Identifier fontName = FONT_DEFINITIONS.fileToId(fontStack.getKey());
            builderFutures.add(CompletableFuture.supplyAsync(() -> {
                List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> builderStack = loadResourceStack(fontStack.getValue(), fontName);
                FontManager.UnresolvedBuilderBundle bundle = new FontManager.UnresolvedBuilderBundle(fontName);
 
                for (Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional> stackEntry : builderStack) {
                    FontManager.BuilderId id = stackEntry.getFirst();
                    FontOption.Filter options = stackEntry.getSecond().filter();
                    stackEntry.getSecond().definition().unpack().ifLeft(provider -> {
                        CompletableFuture<Optional<GlyphProvider>> loadResult = this.safeLoad(id, provider, manager, executor);
                        bundle.add(id, options, loadResult);
                    }).ifRight(reference -> bundle.add(id, options, reference));
                }
 
                return bundle;
            }, executor));
        }
 
        return Util.sequence(builderFutures)
            .thenCompose(
                builders -> {
                    List<CompletableFuture<Optional<GlyphProvider>>> allProviderFutures = builders.stream()
                        .flatMap(FontManager.UnresolvedBuilderBundle::listBuilders)
                        .collect(Util.toMutableList());
                    GlyphProvider.Conditional fallback = createFallbackProvider();
                    allProviderFutures.add(CompletableFuture.completedFuture(Optional.of(fallback.provider())));
                    return Util.sequence(allProviderFutures)
                        .thenCompose(
                            allProviders -> {
                                Map<Identifier, List<GlyphProvider.Conditional>> resolved = this.resolveProviders(builders);
                                CompletableFuture<?>[] finalizers = resolved.values()
                                    .stream()
                                    .map(providers -> CompletableFuture.runAsync(() -> this.finalizeProviderLoading(providers, fallback), executor))
                                    .toArray(CompletableFuture[]::new);
                                return CompletableFuture.allOf(finalizers).thenApply(ignored -> {
                                    List<GlyphProvider> providersToClose = allProviders.stream().flatMap(Optional::stream).toList();
                                    return new FontManager.Preparation(resolved, providersToClose);
                                });
                            }
                        );
                }
            );
    }
 
    private CompletableFuture<Optional<GlyphProvider>> safeLoad(
        FontManager.BuilderId id, GlyphProviderDefinition.Loader provider, ResourceManager manager, Executor executor
    ) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return Optional.of(provider.load(manager));
            } catch (Exception var4) {
                LOGGER.warn("Failed to load builder {}, rejecting", id, var4);
                return Optional.empty();
            }
        }, executor);
    }
 
    private Map<Identifier, List<GlyphProvider.Conditional>> resolveProviders(List<FontManager.UnresolvedBuilderBundle> unresolvedProviders) {
        Map<Identifier, List<GlyphProvider.Conditional>> result = new HashMap<>();
        DependencySorter<Identifier, FontManager.UnresolvedBuilderBundle> sorter = new DependencySorter<>();
        unresolvedProviders.forEach(e -> sorter.addEntry(e.fontId, e));
        sorter.orderByDependencies((id, bundle) -> bundle.resolve(result::get).ifPresent(r -> result.put(id, (List<GlyphProvider.Conditional>)r)));
        return result;
    }
 
    private void finalizeProviderLoading(List<GlyphProvider.Conditional> list, GlyphProvider.Conditional fallback) {
        list.add(0, fallback);
        IntSet supportedGlyphs = new IntOpenHashSet();
 
        for (GlyphProvider.Conditional provider : list) {
            supportedGlyphs.addAll(provider.provider().getSupportedGlyphs());
        }
 
        supportedGlyphs.forEach(codepoint -> {
            if (codepoint != 32) {
                for (GlyphProvider.Conditional providerx : Lists.reverse(list)) {
                    if (providerx.provider().getGlyph(codepoint) != null) {
                        break;
                    }
                }
            }
        });
    }
 
    private static Set<FontOption> getFontOptions(Options options) {
        Set<FontOption> result = EnumSet.noneOf(FontOption.class);
        if (options.forceUnicodeFont().get()) {
            result.add(FontOption.UNIFORM);
        }
 
        if (options.japaneseGlyphVariants().get()) {
            result.add(FontOption.JAPANESE_VARIANTS);
        }
 
        return result;
    }
 
    private void apply(FontManager.Preparation preparations, ProfilerFiller profiler) {
        profiler.push("closing");
        this.anyGlyphs.invalidate();
        this.nonFishyGlyphs.invalidate();
        this.fontSets.values().forEach(FontSet::close);
        this.fontSets.clear();
        this.providersToClose.forEach(GlyphProvider::close);
        this.providersToClose.clear();
        Set<FontOption> fontOptions = getFontOptions(Minecraft.getInstance().options);
        profiler.popPush("reloading");
        preparations.fontSets()
            .forEach(
                (id, newProviders) -> this.fontSets.put(id, this.createFontSet(id, Lists.reverse((List<GlyphProvider.Conditional>)newProviders), fontOptions))
            );
        this.providersToClose.addAll(preparations.allProviders);
        profiler.pop();
        if (!this.fontSets.containsKey(Minecraft.DEFAULT_FONT)) {
            throw new IllegalStateException("Default font failed to load");
        } else {
            this.atlasProviders.clear();
            this.atlasManager.forEach((atlasId, atlasTexture) -> this.atlasProviders.put(atlasId, new AtlasGlyphProvider(atlasTexture)));
        }
    }
 
    public void updateOptions(Options options) {
        Set<FontOption> fontOptions = getFontOptions(options);
 
        for (FontSet value : this.fontSets.values()) {
            value.reload(fontOptions);
        }
    }
 
    private static List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> loadResourceStack(List<Resource> resourceStack, Identifier fontName) {
        List<Pair<FontManager.BuilderId, GlyphProviderDefinition.Conditional>> builderStack = new ArrayList<>();
 
        for (Resource resource : resourceStack) {
            try (Reader reader = resource.openAsReader()) {
                JsonElement jsonContents = GSON.fromJson(reader, JsonElement.class);
                FontManager.FontDefinitionFile definition = FontManager.FontDefinitionFile.CODEC
                    .parse(JsonOps.INSTANCE, jsonContents)
                    .getOrThrow(JsonParseException::new);
                List<GlyphProviderDefinition.Conditional> providers = definition.providers;
 
                for (int i = providers.size() - 1; i >= 0; i--) {
                    FontManager.BuilderId id = new FontManager.BuilderId(fontName, resource.sourcePackId(), i);
                    builderStack.add(Pair.of(id, providers.get(i)));
                }
            } catch (Exception var13) {
                LOGGER.warn("Unable to load font '{}' in {} in resourcepack: '{}'", fontName, "fonts.json", resource.sourcePackId(), var13);
            }
        }
 
        return builderStack;
    }
 
    public Font createFont() {
        return new Font(this.anyGlyphs);
    }
 
    public Font createFontFilterFishy() {
        return new Font(this.nonFishyGlyphs);
    }
 
    private FontSet getFontSetRaw(Identifier id) {
        return this.fontSets.getOrDefault(id, this.missingFontSet);
    }
 
    private GlyphSource getSpriteFont(FontDescription.AtlasSprite contents) {
        AtlasGlyphProvider provider = this.atlasProviders.get(contents.atlasId());
        return provider == null ? this.missingFontSet.source(false) : provider.sourceForSprite(contents.spriteId());
    }
 
    @Override
    public void close() {
        this.anyGlyphs.close();
        this.nonFishyGlyphs.close();
        this.fontSets.values().forEach(FontSet::close);
        this.providersToClose.forEach(GlyphProvider::close);
        this.missingFontSet.close();
    }
 
    @OnlyIn(Dist.CLIENT)
    private record BuilderId(Identifier fontId, String pack, int index) {
        @Override
        public String toString() {
            return "(" + this.fontId + ": builder #" + this.index + " from pack " + this.pack + ")";
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private record BuilderResult(FontManager.BuilderId id, FontOption.Filter filter, Either<CompletableFuture<Optional<GlyphProvider>>, Identifier> result) {
        public Optional<List<GlyphProvider.Conditional>> resolve(Function<Identifier, @Nullable List<GlyphProvider.Conditional>> resolver) {
            return this.result
                .map(
                    provider -> provider.join().map(p -> List.of(new GlyphProvider.Conditional(p, this.filter))),
                    reference -> {
                        List<GlyphProvider.Conditional> resolvedReferences = resolver.apply(reference);
                        if (resolvedReferences == 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",
                                    reference,
                                    this.id
                                );
                            return Optional.empty();
                        } else {
                            return Optional.of(resolvedReferences.stream().map(this::mergeFilters).toList());
                        }
                    }
                );
        }
 
        private GlyphProvider.Conditional mergeFilters(GlyphProvider.Conditional original) {
            return new GlyphProvider.Conditional(original.provider(), this.filter.merge(original.filter()));
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private class CachedFontProvider implements Font.Provider, AutoCloseable {
        private final boolean nonFishyOnly;
        private volatile FontManager.CachedFontProvider.@Nullable CachedEntry lastEntry;
        private volatile @Nullable EffectGlyph whiteGlyph;
 
        private CachedFontProvider(boolean nonFishyOnly) {
            Objects.requireNonNull(FontManager.this);
            super();
            this.nonFishyOnly = nonFishyOnly;
        }
 
        public void invalidate() {
            this.lastEntry = null;
            this.whiteGlyph = null;
        }
 
        @Override
        public void close() {
            this.invalidate();
        }
 
        private GlyphSource getGlyphSource(FontDescription description) {
            return switch (description) {
                case FontDescription.Resource resource -> FontManager.this.getFontSetRaw(resource.id()).source(this.nonFishyOnly);
                case FontDescription.AtlasSprite sprite -> FontManager.this.getSpriteFont(sprite);
                case FontDescription.PlayerSprite player -> FontManager.this.playerProvider.sourceForPlayer(player);
                default -> FontManager.this.missingFontSet.source(this.nonFishyOnly);
            };
        }
 
        @Override
        public GlyphSource glyphs(FontDescription description) {
            FontManager.CachedFontProvider.CachedEntry lastEntry = this.lastEntry;
            if (lastEntry != null && description.equals(lastEntry.description)) {
                return lastEntry.source;
            } else {
                GlyphSource result = this.getGlyphSource(description);
                this.lastEntry = new FontManager.CachedFontProvider.CachedEntry(description, result);
                return result;
            }
        }
 
        @Override
        public EffectGlyph effect() {
            EffectGlyph whiteGlyph = this.whiteGlyph;
            if (whiteGlyph == null) {
                whiteGlyph = FontManager.this.getFontSetRaw(FontDescription.DEFAULT.id()).whiteGlyph();
                this.whiteGlyph = whiteGlyph;
            }
 
            return whiteGlyph;
        }
 
        @OnlyIn(Dist.CLIENT)
        private record CachedEntry(FontDescription description, GlyphSource source) {
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    private record FontDefinitionFile(List<GlyphProviderDefinition.Conditional> providers) {
        public static final Codec<FontManager.FontDefinitionFile> CODEC = RecordCodecBuilder.create(
            i -> i.group(GlyphProviderDefinition.Conditional.CODEC.listOf().fieldOf("providers").forGetter(FontManager.FontDefinitionFile::providers))
                .apply(i, FontManager.FontDefinitionFile::new)
        );
    }
 
    @OnlyIn(Dist.CLIENT)
    private record Preparation(Map<Identifier, List<GlyphProvider.Conditional>> fontSets, List<GlyphProvider> allProviders) {
    }
 
    @OnlyIn(Dist.CLIENT)
    private record UnresolvedBuilderBundle(Identifier fontId, List<FontManager.BuilderResult> builders, Set<Identifier> dependencies)
        implements DependencySorter.Entry<Identifier> {
        public UnresolvedBuilderBundle(Identifier fontId) {
            this(fontId, new ArrayList<>(), new HashSet<>());
        }
 
        public void add(FontManager.BuilderId builderId, FontOption.Filter filter, GlyphProviderDefinition.Reference reference) {
            this.builders.add(new FontManager.BuilderResult(builderId, filter, Either.right(reference.id())));
            this.dependencies.add(reference.id());
        }
 
        public void add(FontManager.BuilderId builderId, FontOption.Filter filter, CompletableFuture<Optional<GlyphProvider>> provider) {
            this.builders.add(new FontManager.BuilderResult(builderId, filter, Either.left(provider)));
        }
 
        private Stream<CompletableFuture<Optional<GlyphProvider>>> listBuilders() {
            return this.builders.stream().flatMap(e -> e.result.left().stream());
        }
 
        public Optional<List<GlyphProvider.Conditional>> resolve(Function<Identifier, List<GlyphProvider.Conditional>> resolver) {
            List<GlyphProvider.Conditional> resolved = new ArrayList<>();
 
            for (FontManager.BuilderResult builder : this.builders) {
                Optional<List<GlyphProvider.Conditional>> resolvedBuilder = builder.resolve(resolver);
                if (!resolvedBuilder.isPresent()) {
                    return Optional.empty();
                }
 
                resolved.addAll(resolvedBuilder.get());
            }
 
            return Optional.of(resolved);
        }
 
        @Override
        public void visitRequiredDependencies(Consumer<Identifier> output) {
            this.dependencies.forEach(output);
        }
 
        @Override
        public void visitOptionalDependencies(Consumer<Identifier> output) {
        }
    }
}

引用的其他类