From 05069aa62b09f02a8cd6e526ec58a30347a56500 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 27 Jul 2022 23:25:27 +0800 Subject: WIP Module --- .../registry/display/DisplayRegistryImpl.java | 322 +++++++++++++++++++++ .../common/registry/RecipeManagerContextImpl.java | 76 +++++ .../display/DisplaySerializerRegistryImpl.java | 77 +++++ 3 files changed, 475 insertions(+) create mode 100644 runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java create mode 100644 runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java create mode 100644 runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/display/DisplaySerializerRegistryImpl.java (limited to 'runtime-engine/displays/src/main/java/me') diff --git a/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java new file mode 100644 index 000000000..bd29f9142 --- /dev/null +++ b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java @@ -0,0 +1,322 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.client.registry.display; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ForwardingMapEntry; +import com.google.common.collect.Iterators; +import dev.architectury.event.EventResult; +import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator; +import me.shedaniel.rei.api.client.registry.display.reason.DisplayAdditionReason; +import me.shedaniel.rei.api.client.registry.display.reason.DisplayAdditionReasons; +import me.shedaniel.rei.api.client.registry.display.visibility.DisplayVisibilityPredicate; +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.plugins.PluginManager; +import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl; +import net.minecraft.world.item.crafting.Recipe; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.mutable.MutableLong; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +public class DisplayRegistryImpl extends RecipeManagerContextImpl implements DisplayRegistry { + private final WeakHashMap displaysBase = new WeakHashMap<>(); + private final Map, DisplaysList> displays = new ConcurrentHashMap<>(); + private final Map, List> unmodifiableDisplays; + private final Map, List>> displayGenerators = new ConcurrentHashMap<>(); + private final List> globalDisplayGenerators = new ArrayList<>(); + private final List visibilityPredicates = new ArrayList<>(); + private final List> fillers = new ArrayList<>(); + private final MutableInt displayCount = new MutableInt(0); + + public DisplayRegistryImpl() { + super(RecipeManagerContextImpl.supplier()); + + this.unmodifiableDisplays = new RemappingMap<>(Collections.unmodifiableMap(displays), list -> { + if (list == null) { + return null; + } else { + return ((DisplaysList) list).unmodifiableList; + } + }); + } + + private static class RemappingMap extends ForwardingMap { + protected final Map map; + protected final UnaryOperator remapper; + + public RemappingMap(Map map, UnaryOperator remapper) { + this.map = map; + this.remapper = remapper; + } + + @Override + @NotNull + protected Map delegate() { + return map; + } + + @Override + public V get(Object key) { + return remapper.apply(super.get(key)); + } + + @SuppressWarnings("UnstableApiUsage") + @Override + @NotNull + public Set> entrySet() { + return this.new StandardEntrySet() { + @Override + public Iterator> iterator() { + return mapIterator(map.entrySet().iterator()); + } + }; + } + + private Iterator> mapIterator(Iterator> iterator) { + return Iterators.transform(iterator, this::mapEntry); + } + + private Entry mapEntry(Entry entry) { + return new ForwardingMapEntry<>() { + @Override + @NotNull + protected Entry delegate() { + return entry; + } + + @Override + public V getValue() { + return remapper.apply(entry.getValue()); + } + }; + } + } + + private static class DisplaysList extends ArrayList { + private final List unmodifiableList; + private final List synchronizedList; + + public DisplaysList() { + this.synchronizedList = Collections.synchronizedList(this); + this.unmodifiableList = Collections.unmodifiableList(synchronizedList); + } + } + + @Override + public void acceptPlugin(REIClientPlugin plugin) { + plugin.registerDisplays(this); + } + + @Override + public int displaySize() { + return displayCount.getValue(); + } + + private MutableLong lastAddWarning = new MutableLong(-1); + + @Override + public void add(Display display, @Nullable Object origin) { + if (!PluginManager.areAnyReloading()) { + if (lastAddWarning != null) { + if (lastAddWarning.getValue() > 0 && System.currentTimeMillis() - lastAddWarning.getValue() > 5000) { + InternalLogger.getInstance().warn("Detected runtime DisplayRegistry modification, this can be extremely dangerous!"); + } + lastAddWarning.setValue(System.currentTimeMillis()); + } + } + + displays.computeIfAbsent(display.getCategoryIdentifier(), location -> new DisplaysList()) + .add(display); + displayCount.increment(); + if (origin != null) { + synchronized (displaysBase) { + displaysBase.put(display, origin); + } + } + } + + @Override + public Map, List> getAll() { + return unmodifiableDisplays; + } + + @Override + public void registerGlobalDisplayGenerator(DynamicDisplayGenerator generator) { + globalDisplayGenerators.add(generator); + InternalLogger.getInstance().debug("Added global display generator: %s", generator); + } + + @Override + public void registerDisplayGenerator(CategoryIdentifier categoryId, DynamicDisplayGenerator generator) { + displayGenerators.computeIfAbsent(categoryId, location -> new ArrayList<>()) + .add(generator); + InternalLogger.getInstance().debug("Added display generator for category [%s]: %s", categoryId, generator); + } + + @Override + public Map, List>> getCategoryDisplayGenerators() { + return Collections.unmodifiableMap(displayGenerators); + } + + @Override + public List> getGlobalDisplayGenerators() { + return Collections.unmodifiableList(globalDisplayGenerators); + } + + @Override + public void registerVisibilityPredicate(DisplayVisibilityPredicate predicate) { + visibilityPredicates.add(predicate); + visibilityPredicates.sort(Comparator.reverseOrder()); + InternalLogger.getInstance().debug("Added display visibility predicate: %s [%.2f priority]", predicate, predicate.getPriority()); + } + + @Override + public boolean isDisplayVisible(Display display) { + DisplayCategory category = (DisplayCategory) CategoryRegistry.getInstance().get(display.getCategoryIdentifier()).getCategory(); + Preconditions.checkNotNull(category, "Failed to resolve category: " + display.getCategoryIdentifier()); + for (DisplayVisibilityPredicate predicate : visibilityPredicates) { + try { + EventResult result = predicate.handleDisplay(category, display); + if (result.interruptsFurtherEvaluation()) { + return result.isEmpty() || result.isTrue(); + } + } catch (Throwable throwable) { + InternalLogger.getInstance().error("Failed to check if the display is visible!", throwable); + } + } + + return true; + } + + @Override + public List getVisibilityPredicates() { + return Collections.unmodifiableList(visibilityPredicates); + } + + @Override + public void registerFiller(Class typeClass, Predicate predicate, Function filler) { + registerFiller(o -> typeClass.isInstance(o) && ((Predicate) predicate).test((T) o), o -> ((Function) filler).apply((T) o)); + } + + @Override + public void registerFiller(Class typeClass, BiPredicate predicate, Function filler) { + fillers.add(new DisplayFiller<>((o, s) -> typeClass.isInstance(o) && ((BiPredicate) predicate).test(o, s), (Function) filler)); + InternalLogger.getInstance().debug("Added display filter: %s for %s", filler, typeClass.getName()); + } + + @Override + public void registerFiller(Predicate predicate, Function filler) { + fillers.add(new DisplayFiller<>((o, s) -> ((Predicate) predicate).test(o), (Function) filler)); + InternalLogger.getInstance().debug("Added display filter: %s", filler); + } + + @Override + public void startReload() { + super.startReload(); + this.displays.clear(); + this.displayGenerators.clear(); + this.visibilityPredicates.clear(); + this.fillers.clear(); + this.displayCount.setValue(0); + } + + @Override + public void endReload() { + if (!fillers.isEmpty()) { + List> allSortedRecipes = getAllSortedRecipes(); + for (int i = allSortedRecipes.size() - 1; i >= 0; i--) { + Recipe recipe = allSortedRecipes.get(i); + addWithReason(recipe, DisplayAdditionReason.RECIPE_MANAGER); + } + } + + for (CategoryIdentifier identifier : displays.keySet()) { + if (CategoryRegistry.getInstance().tryGet(identifier).isEmpty()) { + InternalLogger.getInstance().error("Found displays registered for unknown registry", new IllegalStateException(identifier.toString())); + } + } + + InternalLogger.getInstance().debug("Registered %d displays", displayCount.getValue()); + } + + @Override + public Collection tryFillDisplay(T value, DisplayAdditionReason... reason) { + if (value instanceof Display) return Collections.singleton((Display) value); + List displays = null; + DisplayAdditionReasons reasons = reason.length == 0 ? DisplayAdditionReasons.Impl.EMPTY : new DisplayAdditionReasons.Impl(reason); + for (DisplayFiller filler : fillers) { + Display display = tryFillDisplayGenerics(filler, value, reasons); + if (display != null) { + if (displays == null) displays = Collections.singletonList(display); + else { + if (!(displays instanceof ArrayList)) displays = new ArrayList<>(displays); + displays.add(display); + } + } + } + if (displays != null) { + return displays; + } + return Collections.emptyList(); + } + + private D tryFillDisplayGenerics(DisplayFiller filler, Object value, DisplayAdditionReasons reasons) { + try { + if (filler.predicate.test(value, reasons)) { + return filler.mappingFunction.apply(value); + } + } catch (Throwable e) { + throw new RuntimeException("Failed to fill displays!", e); + } + + return null; + } + + @Override + @Nullable + public Object getDisplayOrigin(Display display) { + return displaysBase.get(display); + } + + private record DisplayFiller( + BiPredicate predicate, + + Function mappingFunction + ) {} +} diff --git a/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java new file mode 100644 index 000000000..fd6392fcf --- /dev/null +++ b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/RecipeManagerContextImpl.java @@ -0,0 +1,76 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.common.registry; + +import dev.architectury.utils.EnvExecutor; +import dev.architectury.utils.GameInstance; +import me.shedaniel.rei.api.common.plugins.REIPlugin; +import me.shedaniel.rei.api.common.registry.RecipeManagerContext; +import net.minecraft.client.Minecraft; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeManager; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class RecipeManagerContextImpl

> implements RecipeManagerContext

{ + private static final Comparator> RECIPE_COMPARATOR = Comparator.comparing((Recipe o) -> o.getId().getNamespace()).thenComparing(o -> o.getId().getPath()); + private final Supplier recipeManager; + private List> sortedRecipes = null; + + public RecipeManagerContextImpl() { + this(supplier()); + } + + public RecipeManagerContextImpl(Supplier recipeManager) { + this.recipeManager = recipeManager; + } + + public static Supplier supplier() { + return () -> EnvExecutor.getEnvSpecific(() -> () -> Minecraft.getInstance().getConnection().getRecipeManager(), + () -> () -> GameInstance.getServer().getRecipeManager()); + } + + @Override + public List> getAllSortedRecipes() { + if (sortedRecipes == null) { + this.sortedRecipes = getRecipeManager().getRecipes().parallelStream().sorted(RECIPE_COMPARATOR).collect(Collectors.toList()); + } + + return Collections.unmodifiableList(sortedRecipes); + } + + @Override + public RecipeManager getRecipeManager() { + return recipeManager.get(); + } + + @Override + public void startReload() { + this.sortedRecipes = null; + } +} diff --git a/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/display/DisplaySerializerRegistryImpl.java b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/display/DisplaySerializerRegistryImpl.java new file mode 100644 index 000000000..c8a9bcf04 --- /dev/null +++ b/runtime-engine/displays/src/main/java/me/shedaniel/rei/impl/common/registry/display/DisplaySerializerRegistryImpl.java @@ -0,0 +1,77 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.impl.common.registry.display; + +import me.shedaniel.rei.api.common.category.CategoryIdentifier; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.display.DisplaySerializer; +import me.shedaniel.rei.api.common.display.DisplaySerializerRegistry; +import me.shedaniel.rei.api.common.plugins.REIPlugin; +import net.minecraft.nbt.CompoundTag; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class DisplaySerializerRegistryImpl implements DisplaySerializerRegistry { + private final Map, DisplaySerializer> serializers = new HashMap<>(); + + @Override + public void register(CategoryIdentifier categoryId, DisplaySerializer serializer) { + serializers.put(categoryId, serializer); + } + + @Override + public void registerNotSerializable(CategoryIdentifier categoryId) { + serializers.remove(categoryId); + } + + @Override + public boolean hasSerializer(CategoryIdentifier categoryId) { + return serializers.containsKey(categoryId); + } + + @Override + public CompoundTag save(D display, CompoundTag tag) { + CategoryIdentifier categoryId = display.getCategoryIdentifier(); + return Objects.requireNonNull((DisplaySerializer) serializers.get(categoryId), "Category " + categoryId + " does not have a display serializer!") + .save(tag, display); + } + + @Override + public D read(CategoryIdentifier categoryId, CompoundTag tag) { + return Objects.requireNonNull((DisplaySerializer) serializers.get(categoryId), "Category " + categoryId + " does not have a display serializer!") + .read(tag); + } + + @Override + public void startReload() { + serializers.clear(); + } + + @Override + public void acceptPlugin(REIPlugin plugin) { + plugin.registerDisplaySerializer(this); + } +} -- cgit