mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
Terrain-first hydrology
- Replace the river package with the hydrology planner, footprint compiler, cave containment and runtime - Add the hydrology pack schema, validator, probes and tests - Wire hydrology into IrisComplex, actuators, mantle, Vision and commands
This commit is contained in:
1 parent
bdaa84975a
commit
48afa0d93f
352 files changed
+57955
-19113
No files matched your search
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.engine.IrisEngineMantle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -41,7 +42,7 @@ public final class MainWorldService {
|
||||
"region",
|
||||
"entities",
|
||||
"poi",
|
||||
"mantle",
|
||||
IrisEngineMantle.STORAGE_FOLDER_NAME,
|
||||
"dimensions/minecraft/overworld",
|
||||
"dimensions/minecraft/the_nether",
|
||||
"dimensions/minecraft/the_end",
|
||||
|
||||
+7
-1
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.engine.IrisEngineMantle;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -33,7 +34,12 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ModdedDimensionStorage {
|
||||
private static final List<String> CHUNK_DATA_FOLDERS = List.of("region", "entities", "poi", "mantle");
|
||||
private static final List<String> CHUNK_DATA_FOLDERS = List.of(
|
||||
"region",
|
||||
"entities",
|
||||
"poi",
|
||||
IrisEngineMantle.STORAGE_FOLDER_NAME
|
||||
);
|
||||
|
||||
private ModdedDimensionStorage() {
|
||||
}
|
||||
|
||||
+1
-1
@@ -325,7 +325,7 @@ public final class IrisModdedCommands {
|
||||
return 0;
|
||||
}
|
||||
String target = downloadDisplayTarget(request);
|
||||
String downloadSource = request.pack() == null ? "direct ZIP URL" : "built-in beta release";
|
||||
String downloadSource = request.pack() == null ? "direct ZIP URL" : "built-in stable release";
|
||||
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
|
||||
if (scheduler == null) {
|
||||
fail(source, IrisLanguage.plain(
|
||||
|
||||
+44
-1
@@ -25,6 +25,8 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.framework.StructureReachability;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyFeatureQuery;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
@@ -62,6 +64,7 @@ final class ModdedCommandSuggestions {
|
||||
static final SuggestionProvider<CommandSourceStack> OBJECT_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder);
|
||||
static final SuggestionProvider<CommandSourceStack> STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder);
|
||||
static final SuggestionProvider<CommandSourceStack> POI_TYPES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("buried_treasure"), builder);
|
||||
static final SuggestionProvider<CommandSourceStack> HYDROLOGY_TYPES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestHydrologyTypes(context, builder);
|
||||
static final SuggestionProvider<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder);
|
||||
static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
|
||||
|
||||
@@ -79,7 +82,7 @@ final class ModdedCommandSuggestions {
|
||||
try {
|
||||
Engine engine = IrisModdedCommands.engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
return SharedSuggestionProvider.suggest(engine.getData().getBiomeLoader().getPossibleKeys(), builder);
|
||||
return SharedSuggestionProvider.suggest(reachableBiomeKeys(engine), builder);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
warnTabFailure("biome keys", context.getSource(), e);
|
||||
@@ -87,6 +90,46 @@ final class ModdedCommandSuggestions {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestHydrologyTypes(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
SuggestionsBuilder builder
|
||||
) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = IrisModdedCommands.engineFor(context.getSource().getLevel());
|
||||
if (engine != null && engine.getComplex().getHydrologyRuntime() != null) {
|
||||
return SharedSuggestionProvider.suggest(
|
||||
engine.getComplex().getHydrologyRuntime().featureQueryKeys(), builder);
|
||||
}
|
||||
} catch (Throwable error) {
|
||||
warnTabFailure("hydrology types", context.getSource(), error);
|
||||
}
|
||||
return SharedSuggestionProvider.suggest(
|
||||
HydrologyFeatureQuery.suggestions(List.of()), builder);
|
||||
}
|
||||
|
||||
static Set<String> reachableBiomeKeys(Engine engine) {
|
||||
return reachableBiomeKeys(engine.getAllBiomes());
|
||||
}
|
||||
|
||||
static Set<String> reachableBiomeKeys(Iterable<IrisBiome> biomes) {
|
||||
Set<String> keys = new TreeSet<>();
|
||||
for (IrisBiome biome : biomes) {
|
||||
if (biome != null && biome.getLoadKey() != null && !biome.getLoadKey().isBlank()) {
|
||||
keys.add(biome.getLoadKey());
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
static boolean isReachableBiome(Engine engine, String biomeKey) {
|
||||
return isReachableBiome(engine.getAllBiomes(), biomeKey);
|
||||
}
|
||||
|
||||
static boolean isReachableBiome(Iterable<IrisBiome> biomes, String biomeKey) {
|
||||
return biomeKey != null && reachableBiomeKeys(biomes).contains(biomeKey.trim());
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestRegionKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
|
||||
+3
@@ -227,6 +227,9 @@ final class ModdedCommandTree {
|
||||
.then(Commands.literal("object")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.OBJECT_KEYS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedLocateCommands.gotoObject(context.getSource(), StringArgumentType.getString(context, "key")))))
|
||||
.then(Commands.literal("river")
|
||||
.then(Commands.argument("type", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.HYDROLOGY_TYPES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedLocateCommands.gotoRiver(context.getSource(), StringArgumentType.getString(context, "type")))))
|
||||
.then(Commands.literal("unregistered")
|
||||
.executes((CommandContext<CommandSourceStack> context) ->
|
||||
ModdedUnregisteredStructures.print(context.getSource())))
|
||||
|
||||
+67
@@ -29,12 +29,16 @@ import art.arcane.iris.engine.framework.Locator;
|
||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||
import art.arcane.iris.engine.framework.StructureReachability;
|
||||
import art.arcane.iris.engine.framework.WrongEngineBroException;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyFeatureQuery;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyFeatureRef;
|
||||
import art.arcane.iris.engine.hydrology.runtime.IrisHydrologyRuntime;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.util.project.context.IrisContext;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
@@ -87,6 +91,10 @@ final class ModdedLocateCommands {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_BIOME_2, MessageArgument.untrusted("key", key)));
|
||||
return 0;
|
||||
}
|
||||
if (!ModdedCommandSuggestions.isReachableBiome(engine, biome.getLoadKey())) {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_DEFINED_DIMENSION, MessageArgument.untrusted("value", biome.getLoadKey())));
|
||||
return 0;
|
||||
}
|
||||
locate(source, level, engine, player, Locator.surfaceBiome(biome.getLoadKey()), "biome " + biome.getLoadKey());
|
||||
return 1;
|
||||
}
|
||||
@@ -137,6 +145,65 @@ final class ModdedLocateCommands {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int gotoRiver(CommandSourceStack source, String type) {
|
||||
ServerPlayer player = source.getPlayer();
|
||||
if (player == null) {
|
||||
IrisModdedCommands.fail(source, "This command can only be used by a player.");
|
||||
return 0;
|
||||
}
|
||||
ServerLevel level = source.getLevel();
|
||||
Engine engine = IrisModdedCommands.engineFor(level);
|
||||
if (engine == null) {
|
||||
IrisModdedCommands.fail(source, "This dimension is not generated by Iris.");
|
||||
return 0;
|
||||
}
|
||||
IrisHydrologyRuntime runtime = engine.getComplex().getHydrologyRuntime();
|
||||
if (runtime == null) {
|
||||
IrisModdedCommands.fail(source, "Hydrology is not active in this dimension.");
|
||||
return 0;
|
||||
}
|
||||
HydrologyFeatureQuery query;
|
||||
try {
|
||||
query = HydrologyFeatureQuery.parse(type);
|
||||
} catch (IllegalArgumentException error) {
|
||||
IrisModdedCommands.fail(source, error.getMessage());
|
||||
return 0;
|
||||
}
|
||||
int originX = player.blockPosition().getX();
|
||||
int originZ = player.blockPosition().getZ();
|
||||
int requestedDistance = Math.min(8192, runtime.settings().routing().tileSize() * 15);
|
||||
int maximumDistance = runtime.maximumFeatureSearchDistance(originX, originZ, requestedDistance);
|
||||
MinecraftServer server = source.getServer();
|
||||
IrisModdedCommands.ok(source, "Searching accepted hydrology plans for " + type + "...");
|
||||
MultiBurst.burst.completeValueAsync(() -> runtime.nearestFeature(
|
||||
query.types(), query.profileKey(), originX, originZ, maximumDistance).orElse(null))
|
||||
.whenComplete((HydrologyFeatureRef feature, Throwable error) -> server.execute(() -> {
|
||||
if (error != null) {
|
||||
ModdedIrisLog.error("Hydrology locate failed for {}", type, error);
|
||||
IrisModdedCommands.fail(source,
|
||||
"Could not locate " + type + " hydrology: " + error.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
if (feature == null) {
|
||||
IrisModdedCommands.fail(source,
|
||||
"No accepted " + type + " hydrology feature found within "
|
||||
+ maximumDistance + " blocks.");
|
||||
return;
|
||||
}
|
||||
int worldY = feature.y() + engine.getDimension().getMinHeight() + 2;
|
||||
teleportToStructure(
|
||||
source,
|
||||
level,
|
||||
player,
|
||||
feature.x(),
|
||||
worldY,
|
||||
feature.z(),
|
||||
type + " hydrology feature"
|
||||
);
|
||||
}));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int gotoStructure(CommandSourceStack source, String keyRaw) {
|
||||
ServerLevel level = source.getLevel();
|
||||
Engine engine = IrisModdedCommands.engineFor(level);
|
||||
|
||||
+4
-6
@@ -341,8 +341,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
markCompleted();
|
||||
listener.onChunkGenerated(x, z);
|
||||
cleanupMantleChunk(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
cleanupMantleChunksCoveredBy(x, z, listener);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
@@ -406,8 +405,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
onSuccess();
|
||||
markCompleted();
|
||||
listener.onChunkGenerated(x, z);
|
||||
cleanupMantleChunk(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
cleanupMantleChunksCoveredBy(x, z, listener);
|
||||
} finally {
|
||||
markFinished();
|
||||
semaphore.release();
|
||||
@@ -494,9 +492,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupMantleChunk(int x, int z) {
|
||||
private void cleanupMantleChunksCoveredBy(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
engine.getMantle().forceCleanupChunk(x, z);
|
||||
engine.getMantle().cleanupChunksCoveredBy(x, z, true, listener::onChunkCleaned);
|
||||
} catch (Throwable e) {
|
||||
ModdedIrisLog.debug("Iris pregen mantle cleanup skipped for {},{}: {}", x, z, e.toString());
|
||||
}
|
||||
|
||||
+1
-1
@@ -301,7 +301,7 @@ public final class ModdedChunkUpdateService implements ModdedTickableService {
|
||||
}
|
||||
}
|
||||
|
||||
private void runUpdatePass(Engine engine, ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
|
||||
void runUpdatePass(Engine engine, ServerLevel level, int chunkX, int chunkZ, MantleChunk<Matter> chunk) {
|
||||
PrecisionStopwatch stopwatch = PrecisionStopwatch.start();
|
||||
int minHeight = engine.getWorld().minHeight();
|
||||
int baseX = chunkX << 4;
|
||||
|
||||
+11
@@ -187,6 +187,17 @@ public class ModdedGenerationLeaseContractTest {
|
||||
assertTrue(logFailure.contains("cause.toString()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pregenReportsOnlyCleanupCallbacksFromCoveredTargets() throws IOException {
|
||||
String source = source("art/arcane/iris/modded/command/ModdedPregenMethod.java");
|
||||
String cleanup = method(source, "private void cleanupMantleChunksCoveredBy(");
|
||||
|
||||
assertTrue(source.contains("cleanupMantleChunksCoveredBy(x, z, listener);"));
|
||||
assertFalse(source.contains("cleanupMantleChunk(x, z);"));
|
||||
assertTrue(cleanup.contains("cleanupChunksCoveredBy(x, z, true, listener::onChunkCleaned)"));
|
||||
assertFalse(cleanup.contains("forceCleanupChunk"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPregenBoundsFailBeforeRuntimeMutation() throws IOException {
|
||||
String jobSource = source("art/arcane/iris/modded/command/ModdedPregenJob.java");
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisModdedBiomeCommandTest {
|
||||
@Test
|
||||
public void biomeKeysAreSortedAndLimitedToReachableBiomes() {
|
||||
KList<IrisBiome> reachable = new KList<>();
|
||||
IrisBiome river = new IrisBiome();
|
||||
river.setLoadKey("river");
|
||||
IrisBiome natural = new IrisBiome();
|
||||
natural.setLoadKey("natural");
|
||||
reachable.add(river);
|
||||
reachable.add(natural);
|
||||
assertEquals(
|
||||
List.of("natural", "river"),
|
||||
List.copyOf(ModdedCommandSuggestions.reachableBiomeKeys(reachable))
|
||||
);
|
||||
assertTrue(ModdedCommandSuggestions.isReachableBiome(reachable, "river"));
|
||||
assertFalse(ModdedCommandSuggestions.isReachableBiome(reachable, "unused"));
|
||||
}
|
||||
}
|
||||
+79
@@ -1,11 +1,27 @@
|
||||
package art.arcane.iris.modded.service;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineMetrics;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.util.project.matter.TileWrapper;
|
||||
import art.arcane.volmlib.util.function.Consumer4;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleDataAdapter;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleHooks;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterUpdate;
|
||||
import art.arcane.volmlib.util.matter.slices.UpdateMatter;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LiquidBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.FlowingFluid;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
@@ -15,8 +31,42 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedChunkUpdateServiceTest {
|
||||
@Test
|
||||
public void fallingFluidUpdateRetainsLevelEightStateAndRequestsPhysics() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
IrisWorld world = mock(IrisWorld.class);
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getWorld()).thenReturn(world);
|
||||
when(engine.getMetrics()).thenReturn(new EngineMetrics(8));
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(level.getMinY()).thenReturn(-64);
|
||||
when(level.getMaxY()).thenReturn(320);
|
||||
BlockPos position = new BlockPos(15, -59, 14);
|
||||
BlockState falling = Blocks.WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 8);
|
||||
when(level.getBlockState(position)).thenReturn(falling);
|
||||
UpdateRecordingMantleChunk chunk = new UpdateRecordingMantleChunk();
|
||||
|
||||
new ModdedChunkUpdateService().runUpdatePass(engine, level, 0, 0, chunk);
|
||||
|
||||
assertEquals(8, falling.getValue(LiquidBlock.LEVEL).intValue());
|
||||
if (falling.getFluidState().hasProperty(FlowingFluid.FALLING)) {
|
||||
assertTrue(falling.getFluidState().getValue(FlowingFluid.FALLING));
|
||||
}
|
||||
InOrder order = inOrder(level);
|
||||
order.verify(level).setBlock(eq(position), eq(Blocks.AIR.defaultBlockState()), anyInt());
|
||||
order.verify(level).setBlock(position, falling, Block.UPDATE_ALL);
|
||||
assertTrue(chunk.deleted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scansWhenPlayersArePresent() {
|
||||
assertTrue(ModdedChunkUpdateService.hasUpdateTargets(true, false));
|
||||
@@ -84,6 +134,35 @@ public class ModdedChunkUpdateServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UpdateRecordingMantleChunk extends MantleChunk<Matter> {
|
||||
private boolean deleted;
|
||||
|
||||
private UpdateRecordingMantleChunk() {
|
||||
super(1, 0, 0, emptyAdapter(), MantleHooks.NONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void iterate(
|
||||
Class<T> type,
|
||||
Consumer4<Integer, Integer, Integer, T> iterator
|
||||
) {
|
||||
if (type == MatterUpdate.class) {
|
||||
iterator.accept(-1, 5, -2, type.cast(UpdateMatter.ON));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSlices(Class<?> type) {
|
||||
if (type == MatterUpdate.class) {
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleted() {
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static MantleDataAdapter<Matter> emptyAdapter() {
|
||||
return (MantleDataAdapter<Matter>) Proxy.newProxyInstance(
|
||||
|
||||
Reference in new issue
Block a user