Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
Bugfix/mod init (#173)
Browse files Browse the repository at this point in the history
* Construct Forge mods at the correct place

* Remove the unused Fabric mod entry

* Remove debug outputs & add comments

* Fix the order of events on client

* Fix crashing on dedicated server

* Fix FMLServerStartingEvent to accommodate the new mod loading procedure

* Fix a mixin applied on the wrong side
  • Loading branch information
rikka0w0 authored Aug 16, 2020
1 parent 146f43b commit a86a132
Show file tree
Hide file tree
Showing 17 changed files with 290 additions and 93 deletions.
1 change: 0 additions & 1 deletion patchwork-dispatcher/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ dependencies {
implementation project(path: ':patchwork-fml', configuration: 'dev')
implementation project(path: ':patchwork-registries', configuration: 'dev')
implementation project(path: ':patchwork-events-lifecycle', configuration: 'dev')
implementation project(path: ':patchwork-events-rendering', configuration: 'dev')
implementation project(path: ':patchwork-model-loader', configuration: 'dev')
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@

package net.patchworkmc.impl;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent;
Expand All @@ -45,40 +45,37 @@
import net.minecraftforge.fml.javafmlmod.FMLModContainer;
import net.minecraftforge.registries.ForgeRegistries;

import net.minecraft.client.MinecraftClient;
import net.minecraft.server.dedicated.DedicatedServer;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;

import net.patchworkmc.api.ForgeInitializer;
import net.patchworkmc.impl.event.lifecycle.LifecycleEvents;
import net.patchworkmc.impl.event.render.RenderEvents;
import net.patchworkmc.impl.modelloader.ModelEventDispatcher;
import net.patchworkmc.impl.registries.RegistryEventDispatcher;

public class Patchwork implements ModInitializer {
public class Patchwork {
private static final Logger LOGGER = LogManager.getLogger(Patchwork.class);

private static void dispatch(Map<ForgeInitializer, FMLModContainer> mods, Event event) {
private static void dispatch(Collection<FMLModContainer> mods, Event event) {
dispatch(mods, container -> event);
}

private static void dispatch(Map<ForgeInitializer, FMLModContainer> mods, Function<ModContainer, Event> provider) {
for (FMLModContainer container : mods.values()) {
/**
* Fire the specific event for all ModContainers on the {@link Mod.EventBusSubscriber.Bus.MOD} Event bus.
*/
private static void dispatch(Collection<FMLModContainer> mods, Function<ModContainer, Event> provider) {
for (FMLModContainer container : mods) {
ModLoadingContext.get().setActiveContainer(container, new FMLJavaModLoadingContext(container));

Event event = provider.apply(container);
LOGGER.debug("Firing event for modid {} : {}", container.getModId(), event.toString());
container.getEventBus().post(event);
container.patchwork$acceptEvent(event);
LOGGER.debug("Fired event for modid {} : {}", container.getModId(), event.toString());

ModLoadingContext.get().setActiveContainer(null, "minecraft");
}
}

@Override
public void onInitialize() {
public static void gatherAndInitializeMods() {
ForgeRegistries.init();

Map<ForgeInitializer, FMLModContainer> mods = new HashMap<>();
Expand All @@ -102,6 +99,7 @@ public void onInitialize() {
ModLoadingContext.get().setActiveContainer(container, new FMLJavaModLoadingContext(container));

try {
// TODO: Supposed to call "container.setMod()" here, but this requires a WIP Patchwork-Patcher feature.
initializer.onForgeInitialize();
} catch (Throwable t) {
if (error == null) {
Expand Down Expand Up @@ -150,27 +148,65 @@ public void onInitialize() {
ModList.get().setLoadedMods(mods.values());
// Send initialization events

dispatch(mods, new RegistryEvent.NewRegistry());
RegistryEventDispatcher.dispatchRegistryEvents(event -> dispatch(mods, event));
dispatch(mods, FMLCommonSetupEvent::new);

DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> {
ModelEventDispatcher.fireModelRegistryEvent();
dispatch(mods, container -> new FMLClientSetupEvent(MinecraftClient::getInstance, container));
RenderEvents.registerEventDispatcher(event -> dispatch(mods, event));
});
dispatch(mods.values(), new RegistryEvent.NewRegistry());
RegistryEventDispatcher.dispatchRegistryEvents(event -> dispatch(mods.values(), event));
}

DistExecutor.runWhenOn(Dist.DEDICATED_SERVER, () -> () -> {
Object gameInstance = FabricLoader.getInstance().getGameInstance();
Supplier<DedicatedServer> supplier = () -> (DedicatedServer) gameInstance;
/**
* This is called on the ResourceLoader's thread when a resource loading happens, i.e. during client start-up or F3+T is pressed.
* Forge fires the FMLCommonSetupEvent and LifeCycleEvents(FMLClientSetupEvent and FMLDedicatedServerSetupEvent) on its own thread in parallel. Sequence cannot be guaranteed.
* IMPORTANT: In Patchwork, we fire all events on the main thread (Client Thread or Server Thread).
* @param lifeCycleEvent
* @param preSidedRunnable Fired before the LifeCycleEvent, on the main thread. Sequence cannot be guaranteed.
* @param postSidedRunnable Fired after the LifeCycleEvent, on the main thread. Sequence cannot be guaranteed.
*/
public static void loadMods(Function<ModContainer, Event> lifeCycleEvent, Consumer<Consumer<Supplier<Event>>> preSidedRunnable, Consumer<Consumer<Supplier<Event>>> postSidedRunnable) {
List<FMLModContainer> mods = ModList.get().applyForEachModContainer(m -> (FMLModContainer) m).collect(Collectors.toList());

// Loading mod config
// TODO: Load client and common configs here

// Mod setup: SETUP
dispatch(mods, FMLCommonSetupEvent::new);
// Mod setup: SIDED SETUP
preSidedRunnable.accept(c -> dispatch(mods, c.get()));
dispatch(mods, lifeCycleEvent);
postSidedRunnable.accept(c -> dispatch(mods, c.get()));
// Mod setup complete
}

dispatch(mods, container -> new FMLDedicatedServerSetupEvent(supplier, container));
});
/**
* In Patchwork, we fire all of following events on the main thread (Client Thread or Server Thread).
*/
public static void finishMods() {
List<FMLModContainer> mods = ModList.get().applyForEachModContainer(m -> (FMLModContainer) m).collect(Collectors.toList());

// Mod setup: ENQUEUE IMC
dispatch(mods, InterModEnqueueEvent::new);
// Mod setup: PROCESS IMC
dispatch(mods, InterModProcessEvent::new);
LifecycleEvents.setLoadCompleteCallback(() -> dispatch(mods, FMLLoadCompleteEvent::new));
// Mod setup: Final completion
dispatch(mods, FMLLoadCompleteEvent::new);
// Freezing data, TODO: do we need freezing?
// GameData.freezeData();
// NetworkRegistry.lock();
}

public static void beginServerModLoading() {
Object gameInstance = FabricLoader.getInstance().getGameInstance();
Supplier<DedicatedServer> supplier = () -> (DedicatedServer) gameInstance;

LOGGER.debug("Patchwork Dedicated Server Mod Loader: Start mod loading.");
Patchwork.gatherAndInitializeMods();
Patchwork.loadMods(container -> new FMLDedicatedServerSetupEvent(supplier, container), dummy -> { }, dummy -> { });
}

public static void endOfServerModLoading() {
LOGGER.debug("Patchwork Dedicated Server Mod Loader: Finish mod loading.");
Patchwork.finishMods();

LOGGER.debug("Patchwork Dedicated Server Mod Loader: Complete mod loading");
// Assume there's no error.
MinecraftForge.EVENT_BUS.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Minecraft Forge, Patchwork Project
* Copyright (c) 2016-2020, 2019-2020
*
* This library 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 version 2.1
* of the License.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package net.patchworkmc.impl;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;

import net.minecraft.resource.ResourceManager;
import net.minecraft.resource.ResourceReloadListener;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.resource.ClientBuiltinResourcePackProvider;
import net.minecraft.client.resource.ClientResourcePackProfile;
import net.minecraft.resource.ReloadableResourceManager;
import net.minecraft.resource.ResourcePackManager;

public class PatchworkClientModLoader {
private static final Logger LOGGER = LogManager.getLogger(PatchworkClientModLoader.class);
private static boolean loading;
private static MinecraftClient mc;

public static void begin(final MinecraftClient minecraft, final ResourcePackManager<ClientResourcePackProfile> defaultResourcePacks,
final ReloadableResourceManager mcResourceManager, ClientBuiltinResourcePackProvider metadataSerializer) {
loading = true;
PatchworkClientModLoader.mc = minecraft;
Patchwork.gatherAndInitializeMods();
mcResourceManager.registerListener(PatchworkClientModLoader::onReload);
}

/**
* @param syncExecutor The main thread executor
*/
private static CompletableFuture<Void> onReload(final ResourceReloadListener.Synchronizer stage, final ResourceManager resourceManager,
final Profiler prepareProfiler, final Profiler executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) {
return CompletableFuture.runAsync(() -> startModLoading(syncExecutor), asyncExecutor)
.thenCompose(stage::whenPrepared)
.thenRunAsync(() -> finishModLoading(syncExecutor), asyncExecutor);
}

private static void startModLoading(Executor mainThreadExecutor) {
LOGGER.debug("Patchwork Client Mod Loader: Start mod loading.");
mainThreadExecutor.execute(() -> Patchwork.loadMods(container -> new FMLClientSetupEvent(() -> PatchworkClientModLoader.mc, container),
PatchworkClientModLoader::preSidedRunnable, PatchworkClientModLoader::postSidedRunnable));
}

private static void preSidedRunnable(Consumer<Supplier<Event>> perModContainerEventProcessor) {
perModContainerEventProcessor.accept(ModelRegistryEvent::new);
}

private static void postSidedRunnable(Consumer<Supplier<Event>> perModContainerEventProcessor) {
}

private static void finishModLoading(Executor executor) {
LOGGER.debug("Patchwork Client Mod Loader: Finish mod loading.");
Patchwork.finishMods();
loading = false;
// reload game settings on main thread
executor.execute(() -> mc.options.load());
}

/**
* @return true if an error occurred so that we can cancel the normal title screen.
*/
public static boolean completeModLoading() {
LOGGER.debug("Patchwork Client Mod Loader: Complete mod loading");
// Assume there's no error.
MinecraftForge.EVENT_BUS.start();
return false;
}

// TODO: Reserved for future use
public static void onResourceReloadComplete(boolean errorFree) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Minecraft Forge, Patchwork Project
* Copyright (c) 2016-2020, 2019-2020
*
* This library 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 version 2.1
* of the License.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package net.patchworkmc.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import net.minecraft.client.MinecraftClient;
import net.minecraft.resource.ReloadableResourceManager;

import net.patchworkmc.impl.PatchworkClientModLoader;

@Mixin(MinecraftClient.class)
public abstract class MixinMinecraftClient {
@Shadow
private ReloadableResourceManager resourceManager;

@Inject(method = "init", at = @At(value = "INVOKE", shift = Shift.BEFORE, ordinal = 0, target = "net/minecraft/resource/ResourcePackManager.scanPacks()V"))
private void initForgeModsOnClient(CallbackInfo ci) {
MinecraftClient me = (MinecraftClient) (Object) this;
PatchworkClientModLoader.begin(me, me.getResourcePackManager(), resourceManager, me.getResourcePackDownloader());
}

// this.setOverlay(new SplashScreen(this, this.resourceManager.beginInitialMonitoredReload(SystemUtil.getServerWorkerExecutor(), this, voidFuture), () -> {
// if (SharedConstants.isDevelopment) this.checkGameData();
// + if (net.minecraftforge.fml.client.ClientModLoader.completeModLoading()) return; // Do not overwrite the error sceen
// + // Show either ConnectScreen or TitleScreen
// }
@Inject(method = "method_18504", at = @At("RETURN"))
private void onResourceReloadComplete(CallbackInfo ci) {
PatchworkClientModLoader.onResourceReloadComplete(!PatchworkClientModLoader.completeModLoading());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,27 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package net.patchworkmc.mixin.event.lifecycle;
package net.patchworkmc.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.integrated.IntegratedServer;

import net.patchworkmc.impl.event.lifecycle.LifecycleEvents;
import net.patchworkmc.impl.Patchwork;

/**
* Mixes into {@link IntegratedServer} and {@link MinecraftDedicatedServer} in order to implement
* {@link net.minecraftforge.fml.event.server.FMLServerStartingEvent}. This event fires right before the implementations
* return <code>true</code> from <code>setupServer</code>. Returning <code>false</code> from the callback cancels the
* server's startup, however, it's important to note that this event isn't actually cancellable in Forge!
*/
@Mixin({IntegratedServer.class, MinecraftDedicatedServer.class})
public class MixinMinecraftServerSubclass {
@Inject(method = "setupServer", at = @At("RETURN"))
private void hookSetupEnd(CallbackInfoReturnable<Boolean> callback) {
LifecycleEvents.handleServerStarting((MinecraftServer) (Object) this);
@Mixin(MinecraftDedicatedServer.class)
public abstract class MixinMinecraftDedicatedServer {
@Inject(method = "setupServer", at = @At(value = "INVOKE", shift = Shift.AFTER, ordinal = 0, target = "org/apache/logging/log4j/Logger.info(Ljava/lang/String;)V"))
private void initForgeModsOnServer(CallbackInfoReturnable<Boolean> ci) {
Patchwork.beginServerModLoading();
}

@Inject(method = "setupServer", at = @At(value = "NEW", shift = Shift.BEFORE, ordinal = 0, target = "net/minecraft/server/dedicated/DedicatedPlayerManager"))
private void endOfModLoading(CallbackInfoReturnable<Boolean> ci) {
Patchwork.endOfServerModLoading();
}
}
11 changes: 5 additions & 6 deletions patchwork-dispatcher/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@
"license": "LGPL-2.1-only",
"icon": "assets/patchwork-dispatcher/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"net.patchworkmc.impl.Patchwork"
]
},
"depends": {
"patchwork-api-base": "*",
"patchwork-fml": "*",
"patchwork-registries": "*",
"patchwork-events-lifecycle": "*"
"patchwork-events-lifecycle": "*",
"patchwork-model-loader": "*"
},
"mixins": [
"patchwork-dispatcher.mixins.json"
],
"custom": {
"modmenu:api": true,
"modmenu:parent": "patchwork"
Expand Down
Loading

0 comments on commit a86a132

Please sign in to comment.