Wire Volume 1 server lifecycle
This commit is contained in:
@@ -7,11 +7,13 @@ import net.neoforged.neoforge.common.NeoForge;
|
|||||||
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
||||||
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
||||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
||||||
|
import net.minecraft.world.level.storage.LevelResource;
|
||||||
|
|
||||||
import com.livingworld.bootstrap.LivingWorldBootstrap;
|
import com.livingworld.bootstrap.LivingWorldBootstrap;
|
||||||
import com.livingworld.core.LivingWorldConstants;
|
import com.livingworld.core.LivingWorldConstants;
|
||||||
import com.livingworld.debug.DiagnosticCategory;
|
import com.livingworld.debug.DiagnosticCategory;
|
||||||
import com.livingworld.debug.LivingWorldLogger;
|
import com.livingworld.debug.LivingWorldLogger;
|
||||||
|
import com.livingworld.platform.neoforge.NeoForgePlatformAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mod entrypoint for Living World.
|
* Mod entrypoint for Living World.
|
||||||
@@ -29,11 +31,17 @@ public class LivingWorldMod {
|
|||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World mod starting...");
|
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "Living World mod starting...");
|
||||||
|
|
||||||
this.bootstrap = new LivingWorldBootstrap();
|
this.bootstrap = new LivingWorldBootstrap();
|
||||||
this.bootstrap.initialize();
|
NeoForgePlatformAdapter platformAdapter = new NeoForgePlatformAdapter(
|
||||||
|
bootstrap::getWorldSaveDirectory,
|
||||||
|
bootstrap::registerCommands,
|
||||||
|
bootstrap::onServerTick);
|
||||||
|
this.bootstrap.initialize(platformAdapter);
|
||||||
|
|
||||||
eventBus.addListener(FMLCommonSetupEvent.class, event -> bootstrap.onCommonSetup());
|
eventBus.addListener(FMLCommonSetupEvent.class, event -> bootstrap.onCommonSetup());
|
||||||
NeoForge.EVENT_BUS.addListener(
|
NeoForge.EVENT_BUS.addListener(
|
||||||
ServerStartingEvent.class, event -> bootstrap.onServerStarting());
|
ServerStartingEvent.class,
|
||||||
|
event -> bootstrap.onServerStarting(
|
||||||
|
event.getServer().getWorldPath(LevelResource.ROOT)));
|
||||||
NeoForge.EVENT_BUS.addListener(
|
NeoForge.EVENT_BUS.addListener(
|
||||||
ServerStartedEvent.class, event -> bootstrap.onServerStarted());
|
ServerStartedEvent.class, event -> bootstrap.onServerStarted());
|
||||||
NeoForge.EVENT_BUS.addListener(
|
NeoForge.EVENT_BUS.addListener(
|
||||||
|
|||||||
@@ -1,48 +1,224 @@
|
|||||||
package com.livingworld.bootstrap;
|
package com.livingworld.bootstrap;
|
||||||
|
|
||||||
|
import com.livingworld.commands.LivingWorldCommandRoot;
|
||||||
|
import com.livingworld.config.DefaultConfigService;
|
||||||
|
import com.livingworld.config.SimulationConfig;
|
||||||
|
import com.livingworld.core.LivingWorldConstants;
|
||||||
|
import com.livingworld.core.services.CoreServices;
|
||||||
|
import com.livingworld.core.services.FileRegionPersistenceService;
|
||||||
|
import com.livingworld.core.services.ServiceRegistry;
|
||||||
|
import com.livingworld.core.simulation.DefaultTimeService;
|
||||||
|
import com.livingworld.core.simulation.SimulationManager;
|
||||||
|
import com.livingworld.core.simulation.SimulationScheduler;
|
||||||
|
import com.livingworld.debug.SimulationProfiler;
|
||||||
import com.livingworld.debug.DiagnosticCategory;
|
import com.livingworld.debug.DiagnosticCategory;
|
||||||
import com.livingworld.debug.LivingWorldLogger;
|
import com.livingworld.debug.LivingWorldLogger;
|
||||||
|
import com.livingworld.events.LivingWorldEventBus;
|
||||||
|
import com.livingworld.modules.ModuleContext;
|
||||||
|
import com.livingworld.modules.ModuleRegistry;
|
||||||
|
import com.livingworld.modules.ServerContext;
|
||||||
|
import com.livingworld.platform.PlatformAdapter;
|
||||||
|
import com.livingworld.regions.RegionFactory;
|
||||||
|
import com.livingworld.regions.RegionLifecycleController;
|
||||||
|
import com.livingworld.regions.RegionManager;
|
||||||
|
import com.livingworld.regions.RegionStorage;
|
||||||
|
import com.livingworld.regions.cache.RegionCache;
|
||||||
|
import com.livingworld.regions.query.RegionQueryEngine;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap class for Living World mod startup lifecycle.
|
* Bootstrap class for Living World mod startup lifecycle.
|
||||||
*
|
|
||||||
* Each method logs its lifecycle stage. No gameplay logic, region/ecosystem,
|
|
||||||
* or persistence systems are implemented yet.
|
|
||||||
*/
|
*/
|
||||||
public class LivingWorldBootstrap {
|
public final class LivingWorldBootstrap {
|
||||||
|
|
||||||
|
private PlatformAdapter platformAdapter;
|
||||||
|
private Path worldSaveDirectory;
|
||||||
|
private ServiceRegistry services;
|
||||||
|
private RegionManager regionManager;
|
||||||
|
private ModuleRegistry moduleRegistry;
|
||||||
|
private SimulationManager simulationManager;
|
||||||
|
private boolean initialized;
|
||||||
|
private boolean serverReady;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once during mod construction.
|
* Called once during mod construction.
|
||||||
*/
|
*/
|
||||||
public void initialize() {
|
public void initialize(PlatformAdapter platformAdapter) {
|
||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "initialize — mod bootstrap initialized.");
|
if (initialized) {
|
||||||
|
throw new IllegalStateException("bootstrap is already initialized");
|
||||||
|
}
|
||||||
|
if (platformAdapter == null) {
|
||||||
|
throw new IllegalArgumentException("platformAdapter must not be null");
|
||||||
|
}
|
||||||
|
this.platformAdapter = platformAdapter;
|
||||||
|
this.platformAdapter.registerCommands();
|
||||||
|
this.platformAdapter.registerServerTickHook();
|
||||||
|
this.platformAdapter.registerPlayerEventHooks();
|
||||||
|
this.initialized = true;
|
||||||
|
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"Platform=" + platformAdapter.getPlatformName()
|
||||||
|
+ ", minecraft=" + platformAdapter.getMinecraftVersion()
|
||||||
|
+ ", loader=" + platformAdapter.getLoaderVersion()
|
||||||
|
+ ", dedicatedServer=" + platformAdapter.isDedicatedServer());
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"initialize - mod bootstrap initialized.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called during common (dedicated & integrated server) setup phase.
|
* Called during common (dedicated & integrated server) setup phase.
|
||||||
*/
|
*/
|
||||||
public void onCommonSetup() {
|
public void onCommonSetup() {
|
||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onCommonSetup — common setup phase reached.");
|
requireInitialized();
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"onCommonSetup - common setup phase reached.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the server is starting.
|
* Called when the server is starting.
|
||||||
*/
|
*/
|
||||||
public void onServerStarting() {
|
public void onServerStarting(Path worldSaveDirectory) {
|
||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStarting — server starting.");
|
requireInitialized();
|
||||||
|
if (worldSaveDirectory == null) {
|
||||||
|
throw new IllegalArgumentException("worldSaveDirectory must not be null");
|
||||||
|
}
|
||||||
|
if (serverReady) {
|
||||||
|
throw new IllegalStateException("server services are already initialized");
|
||||||
|
}
|
||||||
|
this.worldSaveDirectory = worldSaveDirectory;
|
||||||
|
createServerServices(worldSaveDirectory);
|
||||||
|
this.serverReady = true;
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"onServerStarting - services ready at " + worldSaveDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the server has finished starting.
|
* Called when the server has finished starting.
|
||||||
*/
|
*/
|
||||||
public void onServerStarted() {
|
public void onServerStarted() {
|
||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStarted — server started.");
|
requireServerReady();
|
||||||
|
ServerContext context = new ServerContext();
|
||||||
|
moduleRegistry.getEnabledModules().forEach(module -> module.onServerStarted(context));
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"onServerStarted - server started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the server is stopping.
|
* Called when the server is stopping.
|
||||||
*/
|
*/
|
||||||
public void onServerStopping() {
|
public void onServerStopping() {
|
||||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP, "onServerStopping — server stopping.");
|
if (!serverReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
regionManager.flushAll();
|
||||||
|
moduleRegistry.shutdownAll();
|
||||||
|
serverReady = false;
|
||||||
|
LivingWorldLogger.info(
|
||||||
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
|
"onServerStopping - persistence flushed and modules stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServerTick() {
|
||||||
|
if (!serverReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long previousSimulationTick = simulationManager.getSimulationTickCounter();
|
||||||
|
simulationManager.onMinecraftServerTick();
|
||||||
|
if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
|
||||||
|
regionManager.saveDirtyRegions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||||
|
requireInitialized();
|
||||||
|
LivingWorldCommandRoot.registerDeferred(
|
||||||
|
dispatcher,
|
||||||
|
() -> requireService(regionManager, "regionManager"),
|
||||||
|
() -> requireService(moduleRegistry, "moduleRegistry"),
|
||||||
|
() -> requireService(simulationManager, "simulationManager"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getWorldSaveDirectory() {
|
||||||
|
return worldSaveDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isServerReady() {
|
||||||
|
return serverReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceRegistry getServices() {
|
||||||
|
requireServerReady();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createServerServices(Path saveDirectory) {
|
||||||
|
DefaultConfigService configService = new DefaultConfigService();
|
||||||
|
SimulationConfig config = configService.getSimulationConfig();
|
||||||
|
FileRegionPersistenceService persistenceService =
|
||||||
|
new FileRegionPersistenceService(
|
||||||
|
saveDirectory.resolve("living_world"),
|
||||||
|
LivingWorldConstants.MOD_VERSION);
|
||||||
|
RegionCache regionCache = new RegionCache();
|
||||||
|
RegionQueryEngine queryEngine = new RegionQueryEngine(regionCache);
|
||||||
|
regionManager = new RegionManager(
|
||||||
|
new RegionFactory(),
|
||||||
|
new RegionStorage(persistenceService),
|
||||||
|
regionCache,
|
||||||
|
queryEngine,
|
||||||
|
new RegionLifecycleController(),
|
||||||
|
config);
|
||||||
|
moduleRegistry = new ModuleRegistry();
|
||||||
|
LivingWorldEventBus eventBus = new LivingWorldEventBus();
|
||||||
|
DefaultTimeService timeService = new DefaultTimeService();
|
||||||
|
SimulationScheduler scheduler = new SimulationScheduler(config);
|
||||||
|
SimulationProfiler profiler = new SimulationProfiler();
|
||||||
|
simulationManager = new SimulationManager(
|
||||||
|
scheduler,
|
||||||
|
regionManager,
|
||||||
|
moduleRegistry,
|
||||||
|
eventBus,
|
||||||
|
timeService,
|
||||||
|
persistenceService,
|
||||||
|
profiler);
|
||||||
|
|
||||||
|
services = new ServiceRegistry();
|
||||||
|
services.register(CoreServices.CONFIG, configService);
|
||||||
|
services.register(CoreServices.REGIONS, regionManager);
|
||||||
|
services.register(CoreServices.SIMULATION, simulationManager);
|
||||||
|
services.register(CoreServices.PERSISTENCE, persistenceService);
|
||||||
|
services.register(CoreServices.EVENTS, eventBus);
|
||||||
|
services.register(CoreServices.MODULES, moduleRegistry);
|
||||||
|
services.register(CoreServices.TIME, timeService);
|
||||||
|
services.register(CoreServices.DEBUG, profiler);
|
||||||
|
services.lock();
|
||||||
|
|
||||||
|
moduleRegistry.initializeAll(new ModuleContext(services));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireInitialized() {
|
||||||
|
if (!initialized) {
|
||||||
|
throw new IllegalStateException("bootstrap has not been initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requireServerReady() {
|
||||||
|
requireInitialized();
|
||||||
|
if (!serverReady) {
|
||||||
|
throw new IllegalStateException("server services are not ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T requireService(T service, String name) {
|
||||||
|
if (service == null) {
|
||||||
|
throw new IllegalStateException(name + " is not ready");
|
||||||
|
}
|
||||||
|
return service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.livingworld.commands;
|
package com.livingworld.commands;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
@@ -28,6 +29,18 @@ public final class LivingWorldCommandRoot {
|
|||||||
RegionManager regionManager,
|
RegionManager regionManager,
|
||||||
ModuleRegistry moduleRegistry,
|
ModuleRegistry moduleRegistry,
|
||||||
SimulationManager simulationManager) {
|
SimulationManager simulationManager) {
|
||||||
|
registerDeferred(
|
||||||
|
dispatcher,
|
||||||
|
() -> regionManager,
|
||||||
|
() -> moduleRegistry,
|
||||||
|
() -> simulationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerDeferred(
|
||||||
|
CommandDispatcher<CommandSourceStack> dispatcher,
|
||||||
|
Supplier<RegionManager> regionManager,
|
||||||
|
Supplier<ModuleRegistry> moduleRegistry,
|
||||||
|
Supplier<SimulationManager> simulationManager) {
|
||||||
if (dispatcher == null) {
|
if (dispatcher == null) {
|
||||||
throw new IllegalArgumentException("dispatcher must not be null");
|
throw new IllegalArgumentException("dispatcher must not be null");
|
||||||
}
|
}
|
||||||
@@ -45,15 +58,20 @@ public final class LivingWorldCommandRoot {
|
|||||||
.requires(source -> source.hasPermission(OPERATOR_PERMISSION_LEVEL))
|
.requires(source -> source.hasPermission(OPERATOR_PERMISSION_LEVEL))
|
||||||
.then(Commands.literal("status")
|
.then(Commands.literal("status")
|
||||||
.executes(context -> showStatus(
|
.executes(context -> showStatus(
|
||||||
context.getSource(), regionManager, moduleRegistry, simulationManager)))
|
context.getSource(),
|
||||||
|
requireService(regionManager, "regionManager"),
|
||||||
|
requireService(moduleRegistry, "moduleRegistry"),
|
||||||
|
requireService(simulationManager, "simulationManager"))))
|
||||||
.then(Commands.literal("region")
|
.then(Commands.literal("region")
|
||||||
.then(Commands.literal("info")
|
.then(Commands.literal("info")
|
||||||
.executes(context -> RegionInfoCommand.execute(
|
.executes(context -> RegionInfoCommand.execute(
|
||||||
context.getSource(), regionManager))))
|
context.getSource(),
|
||||||
|
requireService(regionManager, "regionManager")))))
|
||||||
.then(Commands.literal("modules")
|
.then(Commands.literal("modules")
|
||||||
.then(Commands.literal("list")
|
.then(Commands.literal("list")
|
||||||
.executes(context -> listModules(
|
.executes(context -> listModules(
|
||||||
context.getSource(), moduleRegistry))))
|
context.getSource(),
|
||||||
|
requireService(moduleRegistry, "moduleRegistry")))))
|
||||||
.then(Commands.literal("simulate")
|
.then(Commands.literal("simulate")
|
||||||
.then(Commands.argument(
|
.then(Commands.argument(
|
||||||
"ticks",
|
"ticks",
|
||||||
@@ -61,10 +79,21 @@ public final class LivingWorldCommandRoot {
|
|||||||
1, SimulationManager.MAX_FORCED_SIMULATION_TICKS))
|
1, SimulationManager.MAX_FORCED_SIMULATION_TICKS))
|
||||||
.executes(context -> SimulateCommand.execute(
|
.executes(context -> SimulateCommand.execute(
|
||||||
context.getSource(),
|
context.getSource(),
|
||||||
simulationManager,
|
requireService(simulationManager, "simulationManager"),
|
||||||
IntegerArgumentType.getInteger(context, "ticks"))))));
|
IntegerArgumentType.getInteger(context, "ticks"))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> T requireService(Supplier<T> supplier, String name) {
|
||||||
|
if (supplier == null) {
|
||||||
|
throw new IllegalArgumentException(name + " supplier must not be null");
|
||||||
|
}
|
||||||
|
T service = supplier.get();
|
||||||
|
if (service == null) {
|
||||||
|
throw new IllegalStateException(name + " is not ready");
|
||||||
|
}
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
private static int showStatus(
|
private static int showStatus(
|
||||||
CommandSourceStack source,
|
CommandSourceStack source,
|
||||||
RegionManager regionManager,
|
RegionManager regionManager,
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public final class LivingWorldConstants {
|
|||||||
/** Human-readable mod name displayed in the mod list. */
|
/** Human-readable mod name displayed in the mod list. */
|
||||||
public static final String MOD_NAME = "Living World";
|
public static final String MOD_NAME = "Living World";
|
||||||
|
|
||||||
|
/** Current mod version. Keep aligned with the Gradle project version. */
|
||||||
|
public static final String MOD_VERSION = "0.1.0";
|
||||||
|
|
||||||
/** Current schema version for data persistence and migration tracking. */
|
/** Current schema version for data persistence and migration tracking. */
|
||||||
public static final int CURRENT_CORE_SCHEMA_VERSION = 1;
|
public static final int CURRENT_CORE_SCHEMA_VERSION = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.livingworld.bootstrap;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.livingworld.core.services.CoreServices;
|
||||||
|
import com.livingworld.core.services.TimeService;
|
||||||
|
import com.livingworld.platform.PlatformAdapter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
class LivingWorldBootstrapTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path temporaryDirectory;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createsAndLocksCompleteServerServiceGraph() {
|
||||||
|
LivingWorldBootstrap bootstrap = new LivingWorldBootstrap();
|
||||||
|
TestPlatformAdapter platform = new TestPlatformAdapter(temporaryDirectory);
|
||||||
|
|
||||||
|
bootstrap.initialize(platform);
|
||||||
|
bootstrap.onCommonSetup();
|
||||||
|
bootstrap.onServerStarting(temporaryDirectory);
|
||||||
|
bootstrap.onServerStarted();
|
||||||
|
|
||||||
|
assertTrue(bootstrap.isServerReady());
|
||||||
|
assertEquals(1, platform.commandRegistrations);
|
||||||
|
assertEquals(1, platform.tickRegistrations);
|
||||||
|
assertTrue(bootstrap.getServices().isLocked());
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.CONFIG));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.REGIONS));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.SIMULATION));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.PERSISTENCE));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.EVENTS));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.MODULES));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.TIME));
|
||||||
|
assertTrue(bootstrap.getServices().isRegistered(CoreServices.DEBUG));
|
||||||
|
|
||||||
|
for (int tick = 0; tick < 100; tick++) {
|
||||||
|
bootstrap.onServerTick();
|
||||||
|
}
|
||||||
|
TimeService timeService = bootstrap.getServices().get(CoreServices.TIME);
|
||||||
|
assertEquals(1, timeService.getSimulationTick());
|
||||||
|
|
||||||
|
bootstrap.onServerStopping();
|
||||||
|
|
||||||
|
assertFalse(bootstrap.isServerReady());
|
||||||
|
assertTrue(Files.exists(temporaryDirectory.resolve("living_world/metadata.properties")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void ignoresTicksUntilServerServicesAreReady() {
|
||||||
|
LivingWorldBootstrap bootstrap = new LivingWorldBootstrap();
|
||||||
|
bootstrap.initialize(new TestPlatformAdapter(temporaryDirectory));
|
||||||
|
|
||||||
|
bootstrap.onServerTick();
|
||||||
|
|
||||||
|
assertFalse(bootstrap.isServerReady());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsServerLifecycleBeforeInitialization() {
|
||||||
|
LivingWorldBootstrap bootstrap = new LivingWorldBootstrap();
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
IllegalStateException.class,
|
||||||
|
() -> bootstrap.onServerStarting(temporaryDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TestPlatformAdapter implements PlatformAdapter {
|
||||||
|
private final Path saveDirectory;
|
||||||
|
private int commandRegistrations;
|
||||||
|
private int tickRegistrations;
|
||||||
|
|
||||||
|
private TestPlatformAdapter(Path saveDirectory) {
|
||||||
|
this.saveDirectory = saveDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String getPlatformName() { return "test"; }
|
||||||
|
@Override public String getMinecraftVersion() { return "test"; }
|
||||||
|
@Override public String getLoaderVersion() { return "test"; }
|
||||||
|
@Override public boolean isDedicatedServer() { return true; }
|
||||||
|
@Override public Path getWorldSaveDirectory() { return saveDirectory; }
|
||||||
|
@Override public void registerCommands() { commandRegistrations++; }
|
||||||
|
@Override public void registerServerTickHook() { tickRegistrations++; }
|
||||||
|
@Override public void registerPlayerEventHooks() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user