Restored the old PluginLoader as AddonLoader

Introduce a new `IAEAddon` interface, which has to be implemented and
annotated wit `@AEAddon` to obtain access to our public API.
This commit is contained in:
yueh
2020-07-11 18:40:51 +02:00
parent d9d491d51c
commit d18eabe1fc
6 changed files with 134 additions and 160 deletions
@@ -26,12 +26,16 @@ import java.lang.annotation.Target;
/**
* Use this annotation on a class in your Mod to have it instantiated during the
* initialization phase of Applied Energistics. AE expects your class to have a
* single constructor and can supply certain arguments to your constructor using
* dependency injection.
* initialization phase of Applied Energistics.
*
* The class also needs to implement {@link IAEAddon}.
*
* AE expects your class to have a single constructor without any parameters.
*
* This is the only way to get access to the public {@link IAppEngApi} instance.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface AEPlugin {
public @interface AEAddon {
}
+1 -1
View File
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
/**
* Marks interfaces that can be used as injectable constructor arguments for an
* {@link AEPlugin}.
* {@link AEAddon}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
+50
View File
@@ -0,0 +1,50 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 TeamAppliedEnergistics
*
* 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 appeng.api;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
/**
* Every AE2 addon requiring access to {@link IAppEngApi}, needs to provide at
* least one class implementing this interface.
*
* Further it requires the class to be annotated with {@link AEAddon}.
*
*/
public interface IAEAddon {
/**
* This gets called once the API is successfully constructed and ready to be
* used.
*
* For now this happens during {@link FMLCommonSetupEvent}.
*
* Each addon is responsible to maintain a reference to {@link IAppEngApi} for
* future use. Otherwise there is no alternative to access it later.
*
* @param api The API instance when ready.
*/
void onAPIAvailable(IAppEngApi api);
}
@@ -0,0 +1,61 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2020, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.core;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import org.objectweb.asm.Type;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.forgespi.language.ModFileScanData.AnnotationData;
import appeng.api.AEAddon;
import appeng.api.IAEAddon;
import appeng.api.IAppEngApi;
/**
* Loads AE addons on startup and provides them with an {@link IAppEngApi}
* instance.
*/
class AddonLoader {
public static void loadAddons(IAppEngApi api) {
final Type annotationType = Type.getType(AEAddon.class);
final Collection<ModFileScanData> allScanData = ModList.get().getAllScanData();
allScanData.stream().map(ModFileScanData::getAnnotations).flatMap(Set::stream)
.filter(a -> Objects.equals(a.getAnnotationType(), annotationType)).map(AnnotationData::getMemberName)
.forEach(className -> {
try {
final Class<?> clazz = Class.forName(className);
final Class<? extends IAEAddon> instanceClass = clazz.asSubclass(IAEAddon.class);
final IAEAddon instance = instanceClass.newInstance();
instance.onAPIAvailable(api);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| LinkageError e) {
AELog.error("Failed to load: %s", className, e);
throw new RuntimeException(e);
}
});
}
}
+13 -6
View File
@@ -71,14 +71,24 @@ import appeng.client.render.cablebus.CableBusModelLoader;
import appeng.client.render.cablebus.P2PTunnelFrequencyModel;
import appeng.client.render.crafting.CraftingCubeModelLoader;
import appeng.client.render.crafting.EncodedPatternModelLoader;
import appeng.client.render.model.*;
import appeng.client.render.model.BiometricCardModel;
import appeng.client.render.model.ColorApplicatorModel;
import appeng.client.render.model.DriveModel;
import appeng.client.render.model.GlassModel;
import appeng.client.render.model.MemoryCardModel;
import appeng.client.render.model.SkyCompassModel;
import appeng.client.render.model.UVLModelLoader;
import appeng.client.render.spatial.SpatialPylonModel;
import appeng.core.crash.ModCrashEnhancement;
import appeng.core.features.registries.PartModels;
import appeng.core.stats.AdvancementTriggers;
import appeng.core.sync.network.NetworkHandler;
import appeng.core.worlddata.WorldData;
import appeng.entity.*;
import appeng.entity.ChargedQuartzEntity;
import appeng.entity.GrowingCrystalEntity;
import appeng.entity.SingularityEntity;
import appeng.entity.TinyTNTPrimedEntity;
import appeng.entity.TinyTNTPrimedRenderer;
import appeng.hooks.TickHandler;
import appeng.integration.Integrations;
import appeng.parts.PartPlacement;
@@ -157,6 +167,7 @@ public final class AppEng {
registerNetworkHandler();
AddonLoader.loadAddons(Api.INSTANCE);
}
@OnlyIn(Dist.CLIENT)
@@ -253,10 +264,6 @@ public final class AppEng {
//
// AELog.info( "Pre Initialization ( ended after " + watch.elapsed( TimeUnit.MILLISECONDS ) + "ms )" );
//
// // Instantiate all Plugins
// List<Object> injectables = Lists.newArrayList(
// AEApi.instance() );
// new PluginLoader().loadPlugins( injectables, event.getAsmData() );
// }
private void startService(final String serviceName, final Thread thread) {
-148
View File
@@ -1,148 +0,0 @@
/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Applied Energistics 2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.core;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import org.objectweb.asm.Type;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.forgespi.language.ModFileScanData;
import appeng.api.AEInjectable;
import appeng.api.AEPlugin;
/**
* Loads AE plugins on startup and provides them with access to various
* components of the AE API.
*/
class PluginLoader {
public void loadPlugins(Collection<Object> injectables) {
Map<Class<?>, Object> injectableMap = mapInjectables(injectables);
findAndInstantiatePlugins(injectableMap);
}
private static void findAndInstantiatePlugins(Map<Class<?>, Object> injectableMap) {
Type aType = Type.getType(AEPlugin.class);
Set<ModFileScanData.AnnotationData> allAnnotated = ModList.get().getAllScanData().stream()
.map(ModFileScanData::getAnnotations).flatMap(Collection::stream)
.filter(a -> a.getAnnotationType().equals(aType)).filter(a -> a.getTargetType() == ElementType.TYPE)
.collect(Collectors.toSet());
for (ModFileScanData.AnnotationData candidate : allAnnotated) {
String cName = candidate.getMemberName();
Class<?> aClass;
try {
aClass = Class.forName(cName);
} catch (ClassNotFoundException e) {
AELog.error(e, "Couldn't find annotated AE plugin class " + cName);
throw new RuntimeException("Couldn't find annotated AE plugin class " + cName, e);
}
// Try instantiating the plugin
try {
Object plugin = instantiatePlugin(aClass, injectableMap);
AELog.info("Loaded AE2 Plugin {}", plugin.getClass());
} catch (Exception e) {
AELog.error(e, "Unable to instantiate AE plugin " + cName);
throw new RuntimeException("Unable to instantiate AE plugin " + cName, e);
}
}
}
private static Object instantiatePlugin(Class<?> aClass, Map<Class<?>, Object> injectableMap) throws Exception {
Constructor<?>[] constructors = aClass.getDeclaredConstructors();
if (constructors.length == 0) {
// This is the default no-arg constructor, although it seems pointless to
// instantiate anything but not take
// any AE dependencies as parameters
return aClass.newInstance();
} else if (constructors.length != 1) {
throw new IllegalArgumentException("Expected a single constructor, but found: " + constructors.length);
}
Constructor<?> constructor = constructors[0];
constructor.setAccessible(true);
Object[] args = findInjectables(constructor, injectableMap);
return constructor.newInstance(args);
}
private static Object[] findInjectables(Constructor<?> constructor, Map<Class<?>, Object> injectableMap) {
Class<?>[] types = constructor.getParameterTypes();
Object[] args = new Object[types.length];
for (int i = 0; i < types.length; i++) {
args[i] = injectableMap.get(types[i]);
if (args[i] == null) {
throw new IllegalArgumentException("Constructor has parameter of type " + types[i]
+ " which is not an injectable type." + " Please see the documentation for @AEPlugin.");
}
}
return args;
}
private static Map<Class<?>, Object> mapInjectables(Collection<Object> injectables) {
ImmutableMap.Builder<Class<?>, Object> builder = ImmutableMap.builder();
for (Object injectable : injectables) {
// Get all super-interfaces that were annotated with @AEInjectable
Set<Class<?>> injectableIfs = getInjectableInterfaces(injectable.getClass());
for (Class<?> injectableIf : injectableIfs) {
builder.put(injectableIf, injectable);
}
}
return builder.build();
}
private static Set<Class<?>> getInjectableInterfaces(Class<?> aClass) {
Set<Class<?>> hierarchy = new HashSet<>();
getFullHierarchy(aClass, hierarchy);
return hierarchy.stream().filter(c -> c.getAnnotation(AEInjectable.class) != null).collect(Collectors.toSet());
}
// Recursively gather all superclasses and superinterfaces of the given class
// and put them into the given collection
private static void getFullHierarchy(Class<?> aClass, Set<Class<?>> classes) {
classes.add(aClass);
for (Class<?> anIf : aClass.getInterfaces()) {
getFullHierarchy(anIf, classes);
}
if (aClass.getSuperclass() != null) {
getFullHierarchy(aClass.getSuperclass(), classes);
}
}
}