Use bound API World Generators

This commit is contained in:
James Seibel
2022-12-10 11:17:50 -06:00
parent eff161fb24
commit d792031c57
7 changed files with 141 additions and 214 deletions
@@ -12,7 +12,7 @@ import com.seibel.lod.core.interfaces.dependencyInjection.IOverrideInjector;
public interface IDhApiOverrideable extends IBindable
{
/**
* Returns when this Override should be used. <br>
* Higher (larger numerical) priorities override lower (smaller numerical) priorities . <br>
* For most developers this can be left at the default.
*/
default int getPriority() { return IOverrideInjector.DEFAULT_NON_CORE_OVERRIDE_PRIORITY; }
@@ -7,20 +7,10 @@ import com.seibel.lod.api.objects.DhApiResult;
* Handles adding world generator overrides.
*
* @author James Seibel
* @version 2022-9-16
* @version 2022-12-10
*/
public interface IDhApiWorldGeneratorOverrideRegister
{
/**
* Registers the given world generator. <Br> <Br>
*
* This registers a backup world generator for all levels and will be overridden if there
* is a world generator for the specific level. <Br>
* If another world generator has already been registered, DhApiResult will return
* the name of the previously registered generator and success = false.
*/
DhApiResult<Void> registerWorldGeneratorOverride(IDhApiWorldGenerator worldGenerator);
/**
* Registers the given world generator for the given level. <Br> <Br>
*
@@ -10,7 +10,7 @@ import com.seibel.lod.core.DependencyInjection.WorldGeneratorInjector;
* Handles adding world generator overrides.
*
* @author James Seibel
* @version 2022-9-16
* @version 2022-12-10
*/
public class DhApiWorldGeneratorOverrideRegister implements IDhApiWorldGeneratorOverrideRegister
{
@@ -20,20 +20,6 @@ public class DhApiWorldGeneratorOverrideRegister implements IDhApiWorldGenerator
@Override
public DhApiResult<Void> registerWorldGeneratorOverride(IDhApiWorldGenerator worldGenerator)
{
try
{
WorldGeneratorInjector.INSTANCE.bind(worldGenerator);
return DhApiResult.createSuccess();
}
catch (Exception e)
{
return DhApiResult.createFail(e.getMessage());
}
}
@Override
public DhApiResult<Void> registerWorldGeneratorOverride(IDhApiLevelWrapper levelWrapper, IDhApiWorldGenerator worldGenerator)
{
@@ -29,102 +29,77 @@ import java.util.HashMap;
/**
* This class takes care of dependency injection for world generators. <Br>
* This is done so other mods can override our world generator(s) to improve or replace them.
*
* @author James Seibel
* @version 2022-11-25
* @version 2022-12-10
*/
public class WorldGeneratorInjector
{
public static final WorldGeneratorInjector INSTANCE = new WorldGeneratorInjector();
private final HashMap<IDhApiLevelWrapper, OverrideInjector> worldGeneratorByLevelWrapper = new HashMap<>();
/** World generators that aren't bound to a specific level and are used if no other world generators are bound. */
private final OverrideInjector backupUniversalWorldGenerators;
/**
* This is used to determine if an override is part of Distant Horizons'
* Core or not.
* This probably isn't the best way of going about this, but it works for now.
*/
private final String corePackagePath;
public WorldGeneratorInjector()
{
String thisPackageName = this.getClass().getPackage().getName();
int secondPackageEndingIndex = StringUtil.nthIndexOf(thisPackageName, ".", 3);
this.corePackagePath = thisPackageName.substring(0, secondPackageEndingIndex); // this should be "com.seibel.lod"
this.backupUniversalWorldGenerators = new OverrideInjector(this.corePackagePath);
}
/** This constructor should only be used for testing different corePackagePaths. */
public WorldGeneratorInjector(String newCorePackagePath)
{
this.corePackagePath = newCorePackagePath;
this.backupUniversalWorldGenerators = new OverrideInjector(this.corePackagePath);
}
/**
* Binds the backup world generator. <Br>
* Binds a world generator to the given level. <Br>
* See {@link DependencyInjector#bind(Class, IBindable) bind(Class, IBindable)} for full documentation.
*
*
* @throws NullPointerException if any parameter is null
* @throws IllegalArgumentException if a non-Distant Horizons world generator with the priority CORE is passed in
*
* @see DependencyInjector#bind(Class, IBindable)
*/
public void bind(IDhApiWorldGenerator worldGeneratorImplementation) throws IllegalStateException, IllegalArgumentException
public void bind(IDhApiLevelWrapper levelForWorldGenerator, IDhApiWorldGenerator worldGeneratorImplementation) throws NullPointerException, IllegalArgumentException
{
this.bind(null, worldGeneratorImplementation);
}
/**
* Binds the world generator to the given level. <Br>
* See {@link DependencyInjector#bind(Class, IBindable) bind(Class, IBindable)} for full documentation.
*
* @throws IllegalArgumentException if a non-Distant Horizons world generator with the priority CORE is passed in
* @see DependencyInjector#bind(Class, IBindable)
*/
public void bind(IDhApiLevelWrapper levelForWorldGenerator, IDhApiWorldGenerator worldGeneratorImplementation) throws IllegalStateException, IllegalArgumentException
{
if (levelForWorldGenerator != null)
// validate inputs
if (levelForWorldGenerator == null)
{
// bind this generator to a specific level
if (!this.worldGeneratorByLevelWrapper.containsKey(levelForWorldGenerator))
{
this.worldGeneratorByLevelWrapper.put(levelForWorldGenerator, new OverrideInjector(this.corePackagePath));
}
this.worldGeneratorByLevelWrapper.get(levelForWorldGenerator).bind(IDhApiWorldGenerator.class, worldGeneratorImplementation);
throw new NullPointerException("A [" + IDhApiLevelWrapper.class.getSimpleName() + "] is required when binding a world generator.");
}
else
if (worldGeneratorImplementation == null)
{
// a null level wrapper binds the generator to all levels
this.backupUniversalWorldGenerators.bind(IDhApiWorldGenerator.class, worldGeneratorImplementation);
throw new NullPointerException("No [" + IDhApiWorldGenerator.class.getSimpleName() + "] given.");
}
// bind this generator to the given level
if (!this.worldGeneratorByLevelWrapper.containsKey(levelForWorldGenerator))
{
this.worldGeneratorByLevelWrapper.put(levelForWorldGenerator, new OverrideInjector(this.corePackagePath));
}
this.worldGeneratorByLevelWrapper.get(levelForWorldGenerator).bind(IDhApiWorldGenerator.class, worldGeneratorImplementation);
}
/**
* Returns the backup world generator with the highest priority. <br>
* See {@link OverrideInjector#get(Class) get(Class)} for more documentation.
*
* @see OverrideInjector#get(Class)
*/
public IDhApiWorldGenerator get() throws ClassCastException
{
return this.backupUniversalWorldGenerators.get(IDhApiWorldGenerator.class);
}
/**
* Returns the bound world generator with the highest priority. <br>
* (Returns a backup world generator if no world generators have been bound for this specific level.) <br>
* See {@link OverrideInjector#get(Class) get(Class)} for more documentation.
*
* Returns null if no world generators have been bound for this specific level. <br><br>
*
* See {@link OverrideInjector#get(Class) get(Class)} for full documentation.
* @see OverrideInjector#get(Class)
*/
public IDhApiWorldGenerator get(IDhApiLevelWrapper levelForWorldGenerator) throws ClassCastException
@@ -132,21 +107,18 @@ public class WorldGeneratorInjector
if (!this.worldGeneratorByLevelWrapper.containsKey(levelForWorldGenerator))
{
// no generator exists for this specific level.
// check for a backup universal world generator
return this.backupUniversalWorldGenerators.get(IDhApiWorldGenerator.class);
return null;
}
// use the existing world generator
return this.worldGeneratorByLevelWrapper.get(levelForWorldGenerator).get(IDhApiWorldGenerator.class);
}
/** Removes all bound world generators. */
public void clear()
{
this.worldGeneratorByLevelWrapper.clear();
this.backupUniversalWorldGenerators.clear();
}
public void clear() { this.worldGeneratorByLevelWrapper.clear(); }
}
@@ -21,6 +21,7 @@ package com.seibel.lod.core.generation;
import com.seibel.lod.api.enums.worldGeneration.EDhApiWorldGenThreadMode;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.core.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.lod.core.level.IDhLevel;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.api.enums.config.EDistanceGenerationMode;
@@ -39,7 +40,7 @@ import java.util.function.Consumer;
/**
* @author Leetom
* @version 2022-11-25
* @version 2022-12-10
*/
public class BatchGenerator implements IDhApiWorldGenerator
{
@@ -61,6 +62,15 @@ public class BatchGenerator implements IDhApiWorldGenerator
//=====================//
// override parameters //
//=====================//
@Override
public int getPriority() { return IOverrideInjector.CORE_PRIORITY; }
//======================//
// generator parameters //
//======================//
@@ -85,20 +95,6 @@ public class BatchGenerator implements IDhApiWorldGenerator
// generator methods //
//===================//
@Override
public void close() { this.stop(true); }
public void stop(boolean blocking)
{
LOGGER.info("Batch Chunk Generator shutting down...");
this.generationGroup.stop(blocking);
}
@Override
public boolean isBusy()
{
return this.generationGroup.getEventCount() > Math.max(Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get().intValue(), 1) * 1.5;
}
@Override
public CompletableFuture<Void> generateChunks(int chunkPosMinX, int chunkPosMinZ, byte granularity, byte targetDataDetail, Consumer<Object[]> resultConsumer)
{
@@ -138,4 +134,25 @@ public class BatchGenerator implements IDhApiWorldGenerator
@Override
public void preGeneratorTaskStart() { this.generationGroup.updateAllFutures(); }
@Override
public boolean isBusy()
{
return this.generationGroup.getEventCount() > Math.max(Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get().intValue(), 1) * 1.5;
}
//=========//
// cleanup //
//=========//
@Override
public void close() { this.stop(true); }
public void stop(boolean blocking)
{
LOGGER.info("Batch Chunk Generator shutting down...");
this.generationGroup.stop(blocking);
}
}
@@ -1,7 +1,6 @@
package com.seibel.lod.core.level;
import com.seibel.lod.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
import com.seibel.lod.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.lod.core.DependencyInjection.WorldGeneratorInjector;
import com.seibel.lod.core.config.AppliedConfigState;
import com.seibel.lod.core.datatype.full.ChunkSizedData;
@@ -372,7 +371,17 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel
WorldGenState(IDhLevel level)
{
this.chunkGenerator = new BatchGenerator(level); // WorldGeneratorInjector.INSTANCE.get(level); //
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
if (worldGenerator == null)
{
// no override generator is bound, use the Core world generator
worldGenerator = new BatchGenerator(level);
// binding the core generator won't prevent other mods from binding their own generators
// since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
}
this.chunkGenerator = worldGenerator;
this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator);
dataFileHandler.setGenerationQueue(this.worldGenerationQueue);
}
@@ -24,7 +24,7 @@ import testItems.worldGeneratorInjection.objects.*;
/**
* @author James Seibel
* @version 2022-9-11
* @version 2022-12-10
*/
public class DependencyInjectorTest
{
@@ -154,114 +154,67 @@ public class DependencyInjectorTest
}
@Test
public void testBackupWorldGeneratorInjection()
public void testWorldGeneratorInjection()
{
WorldGeneratorInjector TEST_INJECTOR = new WorldGeneratorInjector(WorldGeneratorTestAssembly.getPackagePath(2));
WorldGeneratorInjector CORE_INJECTOR = new WorldGeneratorInjector();
// pre-dependency setup
Assert.assertNull("Nothing should have been bound.", TEST_INJECTOR.get());
Assert.assertNull("Nothing should have been bound.", CORE_INJECTOR.get());
// variables to use later
IDhApiWorldGenerator generator;
WorldGeneratorTestCore coreGenerator = new WorldGeneratorTestCore();
WorldGeneratorTestSecondary secondaryGenerator = new WorldGeneratorTestSecondary();
WorldGeneratorTestPrimary primaryGenerator = new WorldGeneratorTestPrimary();
// core generator binding
try { TEST_INJECTOR.bind(coreGenerator); } catch (IllegalArgumentException e) { Assert.fail("Core generator should be bindable for test package injector."); }
try
{
CORE_INJECTOR.bind(coreGenerator);
Assert.fail("Core generator should not be bindable for core package injector.");
}
catch (IllegalArgumentException e) { /* this exception should be thrown */ }
// core override
Assert.assertNotNull("Test injector should've bound core override.", TEST_INJECTOR.get());
Assert.assertNull("Core injector should not have bound core override.", CORE_INJECTOR.get());
// standard get
generator = TEST_INJECTOR.get();
Assert.assertEquals("Override returned incorrect override type.", generator.getPriority(), OverrideInjector.CORE_PRIORITY);
Assert.assertEquals("Incorrect generator returned.", generator.getThreadingMode(), WorldGeneratorTestCore.THREAD_MODE);
// secondary override
TEST_INJECTOR.bind(secondaryGenerator);
// priority gets
generator = TEST_INJECTOR.get();
Assert.assertEquals("Override returned incorrect override type.", generator.getPriority(), WorldGeneratorTestSecondary.PRIORITY);
Assert.assertEquals("Incorrect override object returned.", generator.getThreadingMode(), WorldGeneratorTestSecondary.THREAD_MODE);
// primary override
TEST_INJECTOR.bind(primaryGenerator);
// priority gets
generator = TEST_INJECTOR.get();
Assert.assertEquals("Override returned incorrect override type.", generator.getPriority(), WorldGeneratorTestPrimary.PRIORITY);
Assert.assertEquals("Incorrect override object returned.", generator.getThreadingMode(), WorldGeneratorTestPrimary.THREAD_MODE);
// in-line get
// (make sure the returned type is correct and compiles, the actual value doesn't matter)
EDhApiWorldGenThreadMode threadMode = TEST_INJECTOR.get().getThreadingMode();
}
@Test
public void testSpecificLevelWorldGeneratorInjection()
{
WorldGeneratorInjector TEST_INJECTOR = new WorldGeneratorInjector(WorldGeneratorTestAssembly.getPackagePath(2));
// pre-dependency setup
Assert.assertNull("Nothing should have been bound.", TEST_INJECTOR.get());
// variables to use later
IDhApiWorldGenerator generator;
WorldGeneratorTestCore backupGenerator = new WorldGeneratorTestCore();
WorldGeneratorTestPrimary levelGenerator = new WorldGeneratorTestPrimary();
WorldGeneratorTestCore coreLevelGenerator = new WorldGeneratorTestCore();
WorldGeneratorTestPrimary primaryLevelGenerator = new WorldGeneratorTestPrimary();
WorldGeneratorTestSecondary secondaryLevelGenerator = new WorldGeneratorTestSecondary();
IDhApiLevelWrapper boundLevel = new LevelWrapperTest();
IDhApiLevelWrapper unboundLevel = new LevelWrapperTest();
// backup generator binding
try { TEST_INJECTOR.bind(backupGenerator); } catch (IllegalArgumentException e) { Assert.fail("Core generator should be bindable for test package injector."); }
// get backup generator
generator = TEST_INJECTOR.get();
Assert.assertNotNull("Backup generator not bound.", generator);
Assert.assertEquals("Incorrect backup generator bound.", generator.getPriority(), OverrideInjector.CORE_PRIORITY);
Assert.assertEquals("Incorrect backup generator bound.", generator.getThreadingMode(), WorldGeneratorTestCore.THREAD_MODE);
// bind level specific
try { TEST_INJECTOR.bind(boundLevel, levelGenerator); } catch (IllegalArgumentException e) { Assert.fail("Core generator should be bindable for test package injector."); }
// get bound level generator
// validate nothing has been bound yet
Assert.assertNull("Nothing should have been bound yet.", TEST_INJECTOR.get(boundLevel));
// bind the core generator //
try { TEST_INJECTOR.bind(boundLevel, coreLevelGenerator); } catch (IllegalArgumentException e) { Assert.fail("[" + coreLevelGenerator.getClass().getSimpleName() + "] should be bindable for test package injector."); }
// validate the core generator was bound
generator = TEST_INJECTOR.get(boundLevel);
Assert.assertNotNull("Level generator not bound.", generator);
Assert.assertEquals("Incorrect level generator bound.", generator.getPriority(), WorldGeneratorTestCore.PRIORITY);
Assert.assertEquals("Incorrect level generator bound.", generator.getThreadingMode(), WorldGeneratorTestCore.THREAD_MODE);
// unbound level should still return null
Assert.assertNull("Nothing should have been bound to this level.", TEST_INJECTOR.get(unboundLevel));
// bind the secondary generator //
try { TEST_INJECTOR.bind(boundLevel, secondaryLevelGenerator); } catch (IllegalArgumentException e) { Assert.fail("[" + secondaryLevelGenerator.getClass().getSimpleName() + "] should be bindable for test package injector."); }
// validate the secondary generator overrides the core generator
generator = TEST_INJECTOR.get(boundLevel);
Assert.assertNotNull("Level generator not bound.", generator);
Assert.assertEquals("Incorrect level generator bound.", generator.getPriority(), WorldGeneratorTestSecondary.PRIORITY);
Assert.assertEquals("Incorrect level generator bound.", generator.getThreadingMode(), WorldGeneratorTestSecondary.THREAD_MODE);
// the unbound level should still return null
Assert.assertNull("Nothing should have been bound to this level.", TEST_INJECTOR.get(unboundLevel));
// bind the primary generator //
try { TEST_INJECTOR.bind(boundLevel, primaryLevelGenerator); } catch (IllegalArgumentException e) { Assert.fail("[" + primaryLevelGenerator.getClass().getSimpleName() + "] should be bindable for test package injector."); }
// validate the primary generator overrides both the core and secondary generator
generator = TEST_INJECTOR.get(boundLevel);
Assert.assertNotNull("Level generator not bound.", generator);
Assert.assertEquals("Incorrect level generator bound.", generator.getPriority(), WorldGeneratorTestPrimary.PRIORITY);
Assert.assertEquals("Incorrect level generator bound.", generator.getThreadingMode(), WorldGeneratorTestPrimary.THREAD_MODE);
// get unbound level generator
generator = TEST_INJECTOR.get(unboundLevel);
Assert.assertNotNull("Backup level generator not bound.", generator);
Assert.assertEquals("Incorrect level generator bound.", generator.getPriority(), OverrideInjector.CORE_PRIORITY);
Assert.assertEquals("Incorrect level generator bound.", generator.getThreadingMode(), WorldGeneratorTestCore.THREAD_MODE);
// the unbound level should still return null
Assert.assertNull("Nothing should have been bound to this level.", TEST_INJECTOR.get(unboundLevel));
}
}