1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 12:10:30 +00:00

Load the CC API with services loaders

This is a little odd (it's more complex for one!), but means we can
reuse the internal API interface in other classes, which is useful for
the data provider refactor I'm about to do.

This is much nicer in Java 17 :D (records, ServiceLoader.stream()),
but such is the perils of still targetting 1.16.
This commit is contained in:
Jonathan Coates 2022-10-21 23:50:44 +01:00
parent 1e703f1b07
commit 9db3e6d2a0
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
11 changed files with 279 additions and 79 deletions

View File

@ -15,6 +15,7 @@ plugins {
}
import cc.tweaked.gradle.ExtensionsKt
import cc.tweaked.gradle.IlluaminateExec
import cc.tweaked.gradle.IlluaminateExecToDir
import org.apache.tools.ant.taskdefs.condition.Os
@ -136,6 +137,9 @@ dependencies {
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
annotationProcessor 'org.spongepowered:mixin:0.8.4:processor'
compileOnly(libs.jetbrainsAnnotations)
ExtensionsKt.annotationProcessorEverywhere(dependencies, libs.autoService)
extraModsCompileOnly fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104:api")
extraModsRuntimeOnly fg.deobf("mezz.jei:jei-1.16.5:7.7.0.104")
@ -144,9 +148,6 @@ dependencies {
shade 'org.squiddev:Cobalt:0.5.7'
testCompileOnly(libs.autoService)
testAnnotationProcessor(libs.autoService)
testImplementation(libs.bundles.test)
testImplementation(libs.bundles.kotlin)
testRuntimeOnly(libs.bundles.testRuntime)
@ -442,13 +443,7 @@ def testServer = tasks.register("testServer", JavaExec.class) {
// Copy from runTestServer. We do it in this slightly odd way as runTestServer
// isn't created until the task is configured (which is no good for us).
JavaExec exec = tasks.getByName("runTestServer")
dependsOn(exec.getDependsOn())
exec.copyTo(it)
setClasspath(exec.getClasspath())
mainClass = exec.mainClass
javaLauncher = exec.javaLauncher
setArgs(exec.getArgs())
ExtensionsKt.copyToFull(tasks.getByName("runTestServer"), it)
// Jacoco and modlauncher don't play well together as the classes loaded in-game don't
// match up with those written to disk. We get Jacoco to dump all classes to disk, and

View File

@ -0,0 +1,20 @@
package cc.tweaked.gradle
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.tasks.JavaExec
fun DependencyHandler.annotationProcessorEverywhere(dep: Any) {
add("compileOnly", dep)
add("annotationProcessor", dep)
add("testCompileOnly", dep)
add("testAnnotationProcessor", dep)
}
fun JavaExec.copyToFull(spec: JavaExec) {
copyTo(spec)
spec.classpath = classpath
spec.mainClass.set(mainClass)
spec.javaLauncher.set(javaLauncher)
spec.args = args
}

View File

@ -1,5 +1,6 @@
[versions]
autoService = "1.0.1"
jetbrainsAnnotations = "23.0.0"
kotlin = "1.7.10"
kotlin-coroutines = "1.6.0"
@ -10,6 +11,7 @@ junit = "5.9.1"
[libraries]
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
jetbrainsAnnotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }

View File

@ -5,7 +5,7 @@
*/
package dan200.computercraft;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import com.google.auto.service.AutoService;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
@ -23,6 +23,7 @@ import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
@ -49,16 +50,11 @@ import java.io.InputStream;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
public final class ComputerCraftAPIImpl implements IComputerCraftAPI
@AutoService( ComputerCraftAPIService.class )
public final class ComputerCraftAPIImpl implements ComputerCraftAPIService
{
public static final ComputerCraftAPIImpl INSTANCE = new ComputerCraftAPIImpl();
private String version;
private ComputerCraftAPIImpl()
{
}
public static InputStream getResourceFile( MinecraftServer server, String domain, String subPath )
{
IResourceManager manager = server.getDataPackRegistries().getResourceManager();

View File

@ -22,6 +22,7 @@ import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
@ -267,64 +268,9 @@ public final class ComputerCraftAPI
return getInstance().getWiredElementAt( world, pos, side );
}
private static IComputerCraftAPI instance;
@Nonnull
private static IComputerCraftAPI getInstance()
private static ComputerCraftAPIService getInstance()
{
if( instance != null ) return instance;
try
{
return instance = (IComputerCraftAPI) Class.forName( "dan200.computercraft.ComputerCraftAPIImpl" )
.getField( "INSTANCE" ).get( null );
}
catch( ReflectiveOperationException e )
{
throw new IllegalStateException( "Cannot find ComputerCraft API", e );
}
}
public interface IComputerCraftAPI
{
@Nonnull
String getInstalledVersion();
int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath );
@Nullable
IWritableMount createSaveDirMount( @Nonnull World world, @Nonnull String subPath, long capacity );
@Nullable
IMount createResourceMount( @Nonnull String domain, @Nonnull String subPath );
void registerPeripheralProvider( @Nonnull IPeripheralProvider provider );
void registerGenericSource( @Nonnull GenericSource source );
void registerGenericCapability( @Nonnull Capability<?> capability );
void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade );
void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider );
int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
void registerMediaProvider( @Nonnull IMediaProvider provider );
void registerPocketUpgrade( @Nonnull IPocketUpgrade upgrade );
@Nonnull
IPacketNetwork getWirelessNetwork();
void registerAPIFactory( @Nonnull ILuaAPIFactory factory );
<T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider );
@Nonnull
IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element );
@Nonnull
LazyOptional<IWiredElement> getWiredElementAt( @Nonnull IBlockReader world, @Nonnull BlockPos pos, @Nonnull Direction side );
return ComputerCraftAPIService.get();
}
}

View File

@ -0,0 +1,103 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Backing interface for {@link ComputerCraftAPI}
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface ComputerCraftAPIService
{
static ComputerCraftAPIService get()
{
ComputerCraftAPIService instance = Instance.INSTANCE;
return instance == null ? Services.raise( ComputerCraftAPIService.class, Instance.ERROR ) : instance;
}
@Nonnull
String getInstalledVersion();
int createUniqueNumberedSaveDir( @Nonnull World world, @Nonnull String parentSubPath );
@Nullable
IWritableMount createSaveDirMount( @Nonnull World world, @Nonnull String subPath, long capacity );
@Nullable
IMount createResourceMount( @Nonnull String domain, @Nonnull String subPath );
void registerPeripheralProvider( @Nonnull IPeripheralProvider provider );
void registerGenericSource( @Nonnull GenericSource source );
void registerGenericCapability( @Nonnull Capability<?> capability );
void registerTurtleUpgrade( @Nonnull ITurtleUpgrade upgrade );
void registerBundledRedstoneProvider( @Nonnull IBundledRedstoneProvider provider );
int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
void registerMediaProvider( @Nonnull IMediaProvider provider );
void registerPocketUpgrade( @Nonnull IPocketUpgrade upgrade );
@Nonnull
IPacketNetwork getWirelessNetwork();
void registerAPIFactory( @Nonnull ILuaAPIFactory factory );
<T> void registerDetailProvider( @Nonnull Class<T> type, @Nonnull IDetailProvider<T> provider );
@Nonnull
IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element );
@Nonnull
LazyOptional<IWiredElement> getWiredElementAt( @Nonnull IBlockReader world, @Nonnull BlockPos pos, @Nonnull Direction side );
class Instance
{
static final @Nullable ComputerCraftAPIService INSTANCE;
static final @Nullable Throwable ERROR;
static
{
Services.LoadedService<ComputerCraftAPIService> helper = Services.tryLoad( ComputerCraftAPIService.class );
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance()
{
}
}
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* A ComputerCraft-related service failed to load.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
class ServiceException extends RuntimeException
{
private static final long serialVersionUID = -8392300691666423882L;
ServiceException( String message, @Nullable Throwable cause )
{
super( message, cause );
}
}

View File

@ -0,0 +1,111 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
/**
* Utilities for loading services.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public final class Services
{
private Services()
{
}
/**
* Load a service, asserting that only a single instance is registered.
*
* @param klass The class of the service to load.
* @param <T> The class of the service to load.
* @return The constructed service instance.
* @throws IllegalStateException When the service cannot be loaded.
*/
public static <T> T load( Class<T> klass )
{
List<T> services = new ArrayList<>( 1 );
for( T provider : ServiceLoader.load( klass ) ) services.add( provider );
switch( services.size() )
{
case 1:
return services.get( 0 );
case 0:
throw new IllegalStateException( "Cannot find service for " + klass.getName() );
default:
String serviceTypes = services.stream().map( x -> x.getClass().getName() ).collect( Collectors.joining( ", " ) );
throw new IllegalStateException( "Multiple services for " + klass.getName() + ": " + serviceTypes );
}
}
/**
* Attempt to load a service with {@link #load(Class)}.
*
* @param klass The class of the service to load.
* @param <T> The class of the service to load.
* @return The result type, either containing the service or an exception.
* @see ComputerCraftAPIService Intended usage of this class.
*/
public static <T> LoadedService<T> tryLoad( Class<T> klass )
{
try
{
return new LoadedService<>( load( klass ), null );
}
catch( Exception | LinkageError e )
{
return new LoadedService<>( null, e );
}
}
/**
* Raise an exception from trying to load a specific service.
*
* @param klass The class of the service we failed to load.
* @param e The original exception caused by loading this class.
* @param <T> The class of the service to load.
* @return Never
* @see #tryLoad(Class)
* @see LoadedService#error()
*/
public static <T> T raise( Class<T> klass, @Nullable Throwable e )
{
// Throw a new exception so there's a useful stack trace there somewhere!
throw new ServiceException( "Failed to instantiate " + klass.getName(), e );
}
public static class LoadedService<T>
{
private final @Nullable T instance;
private final @Nullable Throwable error;
LoadedService( @Nullable T instance, @Nullable Throwable error )
{
this.instance = instance;
this.error = error;
}
@Nullable
public T instance()
{
return instance;
}
@Nullable
public Throwable error()
{
return error;
}
}
}

View File

@ -5,11 +5,11 @@
*/
package dan200.computercraft.shared.peripheral.modem.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.wired.WiredNode;
import javax.annotation.Nonnull;
import java.util.HashMap;
@ -17,7 +17,7 @@ import java.util.Map;
public abstract class WiredModemElement implements IWiredElement
{
private final IWiredNode node = new WiredNode( this );
private final IWiredNode node = ComputerCraftAPI.createWiredNodeForElement( this );
private final Map<String, IPeripheral> remotePeripherals = new HashMap<>();
@Nonnull

View File

@ -6,6 +6,7 @@
package dan200.computercraft.shared.peripheral.modem.wireless;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.shared.peripheral.modem.ModemPeripheral;
import dan200.computercraft.shared.peripheral.modem.ModemState;
@ -61,6 +62,6 @@ public abstract class WirelessModemPeripheral extends ModemPeripheral
@Override
protected IPacketNetwork getNetwork()
{
return WirelessNetwork.getUniversal();
return ComputerCraftAPI.getWirelessNetwork();
}
}

View File

@ -35,7 +35,7 @@ public class UploadFileMessageTest
*
* @param sentFiles The files to send.
*/
@Property( tries = 500 )
@Property( tries = 200 )
@Tag( "slow" )
public void testRoundTrip( @ForAll( "fileUploads" ) List<FileUpload> sentFiles )
{