diff --git a/src/mantle/pulsar/config/IConfiguration.java b/src/mantle/pulsar/config/IConfiguration.java old mode 100644 new mode 100755 diff --git a/src/mantle/pulsar/control/PulseManager.java b/src/mantle/pulsar/control/PulseManager.java old mode 100644 new mode 100755 index 5f3a1996..557aa537 --- a/src/mantle/pulsar/control/PulseManager.java +++ b/src/mantle/pulsar/control/PulseManager.java @@ -1,35 +1,44 @@ package mantle.pulsar.control; import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.*; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.SubscriberExceptionContext; +import com.google.common.eventbus.SubscriberExceptionHandler; import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.FMLModContainer; import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; +import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.common.event.FMLEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; + import mantle.pulsar.config.IConfiguration; import mantle.pulsar.internal.Configuration; +import mantle.pulsar.internal.CrashHandler; import mantle.pulsar.pulse.PulseMeta; import mantle.pulsar.internal.logging.ILogger; import mantle.pulsar.internal.logging.LogManager; -import mantle.pulsar.pulse.Handler; -import mantle.pulsar.pulse.IPulse; import mantle.pulsar.pulse.Pulse; -import mantle.pulsar.pulse.PulseProxy; /** * Manager class for a given mods Pulses. * + * This MUST be constructed by a mod BEFORE preinit as it registers on to the mod event bus - a static block would serve + * for this. No more Pulses can be registered after preinit has been caught, so assume preinit is too late to register + * new Pulses. + * * Each Pulsar-enabled mod should create one and only one of these to manage its Pulses. * * @author Arkan */ -@SuppressWarnings({"unused", "deprecated"}) +@SuppressWarnings("unused") public class PulseManager { - private final ILogger log; + private ILogger log; private final boolean useConfig; private final LinkedHashMap pulses = new LinkedHashMap(); @@ -37,31 +46,18 @@ public class PulseManager { private boolean blockNewRegistrations = false; private boolean configLoaded = false; private IConfiguration conf; - - /** - * Configuration-less constructor. - * - * PulseManagers created this way ignore configurability on child Pulses. This is not recommended. - * - * @param modId The parents ModID. - */ - @Deprecated - public PulseManager(String modId) { - log = LogManager.getLogger("PulseManager-" + modId); - useConfig = false; - conf = null; - } + private EventBus bus; + private String id; /** * Configuration-using constructor. * - * This form creates a PulseManager that supports configuration of Pulses by file. Recommended approach. + * This form creates a PulseManager that supports configuration of Pulses by file. * - * @param modId The parents ModID. * @param configName The config file name. */ - public PulseManager(String modId, String configName) { - log = LogManager.getLogger("PulseManager-" + modId); + public PulseManager(String configName) { + init(); useConfig = true; conf = new Configuration(configName, log); } @@ -72,15 +68,27 @@ public PulseManager(String modId, String configName) { * Don't like JSON? Heathen. Lets you handle configuration, to whatever media you like - File, database, death star. * Whatever really. See {@link mantle.pulsar.config.IConfiguration}. * - * @param modId The parents ModID. * @param config Configuration handler. */ - public PulseManager(String modId, IConfiguration config) { - log = LogManager.getLogger("PulseManager-" + modId); + public PulseManager(IConfiguration config) { + init(); useConfig = true; conf = config; } + /** + * Shared initialiser code between all the constructors. + */ + private void init() { + String modId = Loader.instance().activeModContainer().getModId(); + this.id = modId; + log = LogManager.getLogger("Pulsar-" + modId); + FMLCommonHandler.instance().registerCrashCallable(new CrashHandler(modId, this)); + // Attach us to the mods FML bus and setup our own bus + bus = new EventBus(new BusExceptionHandler(modId)); + attachToContainerEventBus(this); + } + /** * Register a new Pulse with the manager. * @@ -97,7 +105,7 @@ public void registerPulse(Object pulse) { } String id, description, deps; - boolean forced, enabled, missingDeps = false; + boolean forced, enabled, defaultEnabled, missingDeps = false; try { Pulse p = pulse.getClass().getAnnotation(Pulse.class); @@ -106,6 +114,7 @@ public void registerPulse(Object pulse) { deps = p.modsRequired(); forced = p.forced(); enabled = p.defaultEnable(); + defaultEnabled = p.defaultEnable(); } catch (NullPointerException ex) { throw new RuntimeException("Could not parse @Pulse annotation for Pulse: " + pulse); } @@ -125,120 +134,65 @@ public void registerPulse(Object pulse) { } } - PulseMeta meta = new PulseMeta(id, description, forced, enabled); + PulseMeta meta = new PulseMeta(id, description, forced, enabled, defaultEnabled); meta.setEnabled(!missingDeps && getEnabledFromConfig(meta)); if (meta.isEnabled()) { - parseAndAddProxies(pulse); pulses.put(pulse, meta); + // Attach Pulse to internal event bus + bus.register(pulse); } } - private boolean getEnabledFromConfig(PulseMeta meta) { - if (meta.isForced() || !useConfig) return true; // Forced or no config set. - - return conf.isModuleEnabled(meta); - } - /** - * @deprecated FML handles proxies now. + * Helper to attach a given object to the modcontainer event bus. * - * @param pulse Pulse to parse for proxy annotations. + * @param obj Object to register. */ - @Deprecated - private void parseAndAddProxies(Object pulse) { + private void attachToContainerEventBus(Object obj) { + ModContainer cnt = Loader.instance().activeModContainer(); + log.debug("Attaching [" + obj + "] to event bus for container [" + cnt + "]"); try { - for (Field f : pulse.getClass().getDeclaredFields()) { - log.debug("Parsing field: " + f); - PulseProxy p = f.getAnnotation(PulseProxy.class); - if (p != null) { // Support for deprecated PulseProxy annotation - log.warn("Pulse " + pulse + " used the deprecated PulseProxy annotation. As of Pulsar 0.1.0, it's now preferred to use FML's SidedProxy annotation."); - log.warn("The old PulseProxy parsing will be removed in the next breaking update (Pulsar 1.x)."); - setProxyField(pulse, f, p.clientSide(), p.serverSide()); - } - } - } catch (Exception ex) { - throw new RuntimeException("Pulse annotation parsing failed for Pulse " + pulse + "; " + ex); - } - } - - @Deprecated - private void setProxyField(Object pulse, Field f, String client, String server) throws Exception { - boolean accessible = f.isAccessible(); - f.setAccessible(true); - switch (FMLCommonHandler.instance().getSide()) { - case CLIENT: - f.set(pulse, Class.forName(client).newInstance()); - break; - default: - f.set(pulse, Class.forName(server).newInstance()); - } - f.setAccessible(accessible); - } - - public void preInit(FMLPreInitializationEvent evt) { - if (!blockNewRegistrations) conf.flush(); // First preInit call, so flush config - blockNewRegistrations = true; - - for (Map.Entry e : pulses.entrySet()) { - if(hasRequiredPulses(e)) { - log.debug("Preinitialising Pulse " + e.getValue().getId() + "..."); - if (e.getKey() instanceof IPulse) { // Deprecated IPulse handling - IPulse ip = (IPulse)e.getKey(); - ip.preInit(evt); - } else findAndInvokeHandlers(e.getKey(), evt); - } - } - } + FMLModContainer mc = (FMLModContainer)cnt; + Field ebf = mc.getClass().getDeclaredField("eventBus"); - public void init(FMLInitializationEvent evt) { - for (Map.Entry e : pulses.entrySet()) { - log.debug("Initialising Pulse " + e.getValue().getId() + "..."); + boolean access = ebf.isAccessible(); + ebf.setAccessible(true); + EventBus eb = (EventBus)ebf.get(mc); + ebf.setAccessible(access); - if (e.getKey() instanceof IPulse) { // Deprecated IPulse handling - IPulse ip = (IPulse)e.getKey(); - ip.init(evt); - log.warn("Pulse " + e.getValue().getId() + " is using the deprecated IPulse interface."); - log.warn("This will be removed in the next major version (Pulsar 1.x) - Please switch to @Handler!"); - } else findAndInvokeHandlers(e.getKey(), evt); - } - } - - public void postInit(FMLPostInitializationEvent evt) { - for (Map.Entry e : pulses.entrySet()) { - log.debug("Postinitialising Pulse " + e.getValue().getId() + "..."); - - if (e.getKey() instanceof IPulse) { // Deprecated IPulse handling - IPulse ip = (IPulse)e.getKey(); - ip.postInit(evt); - } else findAndInvokeHandlers(e.getKey(), evt); + eb.register(obj); + } catch (NoSuchFieldException nsfe) { + throw new RuntimeException("Pulsar >> Incompatible FML mod container (missing eventBus field) - wrong Forge version?"); + } catch (IllegalAccessException iae) { + throw new RuntimeException("Pulsar >> Security Manager blocked access to eventBus on mod container. Cannot continue."); + } catch (ClassCastException cce) { + throw new RuntimeException("Pulsar >> Something in the mod container had the wrong type? " + cce.getMessage()); } } /** - * Parse an object for a matching handler for the given object. + * Internal (but public for EventBus use) handler for events. * - * @param pulse Object to inspect for Handlers - * @param evt The event object + * DO NOT CALL THIS DIRECTLY! Let EventBus handle it. + * + * @param evt An event object. */ - @SuppressWarnings("unchecked") - private void findAndInvokeHandlers(Object pulse, Object evt) { - for (Method m : pulse.getClass().getDeclaredMethods()) { - try { - if (m.getAnnotation(Handler.class) == null) continue; // Ignore non-@Handler methods + @Subscribe + public void propagateEvent(FMLEvent evt) { + if (evt instanceof FMLPreInitializationEvent) preInit((FMLPreInitializationEvent) evt); + bus.post(evt); + } + + private boolean getEnabledFromConfig(PulseMeta meta) { + if (meta.isForced() || !useConfig) return true; // Forced or no config set. - Class[] pTypes = m.getParameterTypes(); - if (pTypes.length != 1) continue; + return conf.isModuleEnabled(meta); + } - Class pt = pTypes[0]; - if (pt.isAssignableFrom(evt.getClass())) { - m.invoke(pulse, evt); - } - } catch (Exception ex) { - log.warn("Caught exception in findAndInvokeHandlers: " + ex); - ex.printStackTrace(); - } - } + public void preInit(FMLPreInitializationEvent evt) { + if (!blockNewRegistrations) conf.flush(); // First preInit call, so flush config + blockNewRegistrations = true; } private boolean hasRequiredPulses(Map.Entry entry) { @@ -255,12 +209,48 @@ private boolean hasRequiredPulses(Map.Entry entry) { return true; } + /** + * Check if a given Pulse ID is loaded in this manager. + * + * @param pulseId The ID to check. + * @return Whether the ID was present. + */ public boolean isPulseLoaded(String pulseId) { for(Map.Entry entry : pulses.entrySet()) { - if(entry.getValue().getId().equals(pulseId)) { + if (entry.getValue().getId().equals(pulseId)) { return true; } } return false; } + + public Collection getAllPulseMetadata() { + return pulses.values(); + } + + /** + * Needed because Google EventBus is a derp and by default swallows exceptions (dafuq guys?) + */ + private class BusExceptionHandler implements SubscriberExceptionHandler { + private final String id; + + /** + * @param id Mod ID to include in exception raises. + */ + public BusExceptionHandler(String id) { + this.id = id; + } + + @Override + public void handleException(Throwable exception, SubscriberExceptionContext ctx) { + FMLCommonHandler.instance().raiseException(exception, "Pulsar/" + id + " >> Exception uncaught in [" + + ctx.getSubscriber().getClass().getName() + ":" + ctx.getSubscriberMethod().getName() + + "] for event [" + ctx.getEvent().getClass().getSimpleName() + "]", true); + } + } + + @Override + public String toString() { + return "PulseManager[" + id + "]"; + } } diff --git a/src/mantle/pulsar/debug/EventSpy.java b/src/mantle/pulsar/debug/EventSpy.java new file mode 100755 index 00000000..6fa6bb5e --- /dev/null +++ b/src/mantle/pulsar/debug/EventSpy.java @@ -0,0 +1,25 @@ +package mantle.pulsar.debug; + +import com.google.common.eventbus.Subscribe; +import mantle.pulsar.internal.logging.ILogger; +import mantle.pulsar.internal.logging.LogManager; +import mantle.pulsar.pulse.Pulse; +import net.minecraftforge.fml.common.Loader; + +/** + * Debug Pulse to 'eavesdrop' on the PulseManager event bus traffic. + * + * @author Arkan + */ +@SuppressWarnings("unused") +@Pulse(id="EventSpy", description="we iz in ur buses, monitorin ur eventz", forced=true) +public class EventSpy { + + private final ILogger log = LogManager.getLogger("EventSpy/" + Loader.instance().activeModContainer().getModId()); + + @Subscribe + public void receive(Object evt) { + log.info("Received event: " + evt); + } + +} diff --git a/src/mantle/pulsar/internal/Configuration.java b/src/mantle/pulsar/internal/Configuration.java old mode 100644 new mode 100755 index 19ee5234..ab5ba547 --- a/src/mantle/pulsar/internal/Configuration.java +++ b/src/mantle/pulsar/internal/Configuration.java @@ -50,7 +50,7 @@ public void load() { public boolean isModuleEnabled(PulseMeta meta) { ConfigEntry entry = modules.get(meta.getId()); if (entry == null) { - modules.put(meta.getId(), new ConfigEntry(meta.isEnabled(), meta.getDescription())); + modules.put(meta.getId(), new ConfigEntry(meta.isDefaultEnabled(), meta.getDescription())); return meta.isEnabled(); } else { return entry.getEnabled(); diff --git a/src/mantle/pulsar/internal/CrashHandler.java b/src/mantle/pulsar/internal/CrashHandler.java new file mode 100755 index 00000000..30a4603a --- /dev/null +++ b/src/mantle/pulsar/internal/CrashHandler.java @@ -0,0 +1,49 @@ +package mantle.pulsar.internal; + +import net.minecraftforge.fml.common.ICrashCallable; +import mantle.pulsar.control.PulseManager; +import mantle.pulsar.pulse.PulseMeta; + +/** + * FML crash callable for Pulse Managers; dumps a list of loaded pulses to the error log. + * + * @author Arkan + */ +public class CrashHandler implements ICrashCallable { + + private String id; + private PulseManager manager; + + public CrashHandler(String modId, PulseManager manager) { + this.id = "Pulsar/" + modId + " loaded Pulses"; + this.manager = manager; + } + + @Override + public String getLabel() { + return id; + } + + @Override + public String call() throws Exception { + String out = "\n"; + for (PulseMeta meta : manager.getAllPulseMetadata()) { + String state = getStateFromMeta(meta); + out += "\t\t- " + meta.getId() + " (" + state + ")\n"; // Yes, yes, manual indenting, I know... + } + return out; + } + + private static String getStateFromMeta(PulseMeta meta) { + if (meta.isForced()) { + return "Enabled/Forced"; + } else { + if (meta.isEnabled()) { + return "Enabled/Not Forced"; + } else { + return "Disabled/Not Forced"; + } + } + } + +} diff --git a/src/mantle/pulsar/internal/logging/ILogger.java b/src/mantle/pulsar/internal/logging/ILogger.java old mode 100644 new mode 100755 diff --git a/src/mantle/pulsar/internal/logging/JULLogger.java b/src/mantle/pulsar/internal/logging/JULLogger.java old mode 100644 new mode 100755 diff --git a/src/mantle/pulsar/internal/logging/Log4jLogger.java b/src/mantle/pulsar/internal/logging/Log4jLogger.java old mode 100644 new mode 100755 diff --git a/src/mantle/pulsar/internal/logging/LogManager.java b/src/mantle/pulsar/internal/logging/LogManager.java old mode 100644 new mode 100755 diff --git a/src/mantle/pulsar/pulse/Handler.java b/src/mantle/pulsar/pulse/Handler.java deleted file mode 100644 index 547648db..00000000 --- a/src/mantle/pulsar/pulse/Handler.java +++ /dev/null @@ -1,14 +0,0 @@ -package mantle.pulsar.pulse; - -import java.lang.annotation.*; - -/** - * Annotates methods to be used for event handling. - * - * @author Arkan - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) -public @interface Handler -{} diff --git a/src/mantle/pulsar/pulse/IPulse.java b/src/mantle/pulsar/pulse/IPulse.java deleted file mode 100644 index 5aaf66e3..00000000 --- a/src/mantle/pulsar/pulse/IPulse.java +++ /dev/null @@ -1,38 +0,0 @@ -package mantle.pulsar.pulse; - -import net.minecraftforge.fml.common.event.FMLInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; - -/** - * Base interface for all Pulsar modules ("pulses"). - * - * @deprecated Use the new {@link Handler} annotation. - * - * @author Arkan - */ -@Deprecated -public interface IPulse { - - /** - * FML preinit from the parent mod. - * - * @param evt The FML event inherited from this Pulses parent. - */ - public void preInit(FMLPreInitializationEvent evt); - - /** - * FML init from the parent mod. - * - * @param evt The FML event inherited from this Pulses parent. - */ - public void init(FMLInitializationEvent evt); - - /** - * FML postinit from the parent mod. - * - * @param evt The FML event inherited from this Pulses parent. - */ - public void postInit(FMLPostInitializationEvent evt); - -} diff --git a/src/mantle/pulsar/pulse/Pulse.java b/src/mantle/pulsar/pulse/Pulse.java old mode 100644 new mode 100755 index 8d4f17cf..9ebce64e --- a/src/mantle/pulsar/pulse/Pulse.java +++ b/src/mantle/pulsar/pulse/Pulse.java @@ -5,6 +5,9 @@ /** * Metadata annotation for IPulse implementations. * + * Pulses should use the standard Google Event Bus @Subscribe annotation to catch FML events, which are forwarded from + * the current mod container (including Pre/Init/Post events). + * * @author Arkan */ @Retention(RetentionPolicy.RUNTIME) diff --git a/src/mantle/pulsar/pulse/PulseMeta.java b/src/mantle/pulsar/pulse/PulseMeta.java old mode 100644 new mode 100755 index a4936384..7059a9ef --- a/src/mantle/pulsar/pulse/PulseMeta.java +++ b/src/mantle/pulsar/pulse/PulseMeta.java @@ -8,13 +8,14 @@ public class PulseMeta { private String id, description; - private boolean forced, enabled; + private boolean forced, enabled, defaultEnabled; - public PulseMeta(String id, String description, boolean forced, boolean enabled) { + public PulseMeta(String id, String description, boolean forced, boolean enabled, boolean defaultEnabled) { this.id = id; this.description = description; this.forced = forced; this.enabled = enabled; + this.defaultEnabled = defaultEnabled; } public String getId() { @@ -36,4 +37,8 @@ public boolean isEnabled() { public void setEnabled(boolean enabled) { this.enabled = enabled; } + + public boolean isDefaultEnabled() { + return defaultEnabled; + } } diff --git a/src/mantle/pulsar/pulse/PulseProxy.java b/src/mantle/pulsar/pulse/PulseProxy.java deleted file mode 100644 index b8089bbd..00000000 --- a/src/mantle/pulsar/pulse/PulseProxy.java +++ /dev/null @@ -1,28 +0,0 @@ -package mantle.pulsar.pulse; - -import java.lang.annotation.*; - -/** - * Annotation to denote a field that should be populated by a side-specific proxy. - * - * @deprecated Use FML's @SidedProxy instead. - * - * @author Arkan - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD}) -@Documented -@Deprecated -public @interface PulseProxy { - - /** - * @return The FQCN of the client proxy. - */ - public String clientSide(); - - /** - * @return The FQCN of the server proxy. - */ - public String serverSide(); - -}