Also updated srg names where used.

Conflicts:
	src/main/scala/li/cil/oc/client/GuiHandler.scala
	src/main/scala/li/cil/oc/common/Proxy.scala
	src/main/scala/li/cil/oc/common/tileentity/Screen.scala
This commit is contained in:
Florian Nücke 2014-02-10 03:45:15 +01:00
commit 2f3f52adc1
13 changed files with 461 additions and 27 deletions

View File

@ -69,4 +69,17 @@ public @interface Callback {
* which is synchronized, so you can consume/produce power in direct calls.
*/
int limit() default Integer.MAX_VALUE;
/**
* A documentation string that is made available to the computers the
* component this callback belongs to is connected to. This allows for
* ingame documentation of callbacks.
* <p/>
* You may want to give a short description of what a method does here, but
* more importantly you should document the expected parameters and return
* type here.
* <p/>
* TODO Actually implement for this to be available to computers...
*/
String doc() default "";
}

View File

@ -7,7 +7,7 @@ package li.cil.oc.api.network;
* those defined via the {@link li.cil.oc.api.network.Callback} annotation, and
* invoking said methods.
*/
public interface ManagedPeripheral extends ManagedEnvironment {
public interface ManagedPeripheral extends Environment {
/**
* Get the list of methods provided by this environment, in
* <em>addition</em> to methods marked as callbacks.

View File

@ -0,0 +1,84 @@
package li.cil.oc.api.network;
/**
* This interface can be used to easily convert tile entities to components,
* without having to implement {@link li.cil.oc.api.network.Environment}
* themselves. The simple implementation will provide no access to OC's internal
* component network, since you won't have access to the node representing the
* tile entity. Use this only for simple cases, where you want to expose a
* couple of methods to the programs running computers.
* <p/>
* This is an interface instead of an annotation, to allow stripping via the
* ever so handy {@link cpw.mods.fml.common.Optional} annotation, meaning there
* will be no strong dependency on OpenComputers.
* <p/>
* Classes implementing this interface will be expanded with the methods
* required for them to function as native block components (say, like the
* screen or keyboard). This means functions in the <tt>Environment</tt>
* interface have to created using a class transformer. If any of the methods
* already exist, this will fail! If things don't work, check your logs, first.
* <p/>
* To expose methods to OC, tag them with {@link li.cil.oc.api.network.Callback}
* and have them use the according signature (see the documentation on the
* <tt>Callback</tt> annotation).
* <p/>
* Alternatively, implement {@link li.cil.oc.api.network.ManagedPeripheral} in
* addition to this interface, to make methods available ComputerCraft style.
* <p/>
* So, in short:
* <ul>
* <li>Implement this interface on a tile entity that should expose
* methods to computers.</li>
* <li>Annotate methods with <tt>Callback</tt> so they exported.</li>
* <li>Alternatively/additionally implement <tt>ManagedPeripheral</tt> to
* provide methods via a list of names and single callback method.</li>
* </ul>
* <p/>
* For example:
* <pre>
* {@literal @}Optional.Interface(iface = "li.cil.oc.api.network.SimpleComponent", modid = "OpenComputers")
* public class TileEntityMyFancyThing extends TileEntity
* implements SimpleComponent
* {
* {@literal @}Callback
* public Object[] greet(Context context, Arguments args) {
* return new Object[]{String.format("Hello, %s!", args.checkString(0))};
* }
* }
* </pre>
* Using the alternative method to provide methods:
* <pre>
* {@literal @}Optional.InterfaceList({
* {@literal @}Optional.Interface(iface = "li.cil.oc.api.network.SimpleComponent", modid = "OpenComputers"),
* {@literal @}Optional.Interface(iface = "li.cil.oc.api.network.ManagedPeripheral", modid = "OpenComputers")
* })
* public class TileEntityMyFancyThing extends TileEntity
* implements SimpleComponent, ManagedPeripheral
* {
* public String[] methods() {
* return new String[] {"greet"};
* }
*
* public Object[] invoke(String method, Context context, Arguments args) {
* if ("greet".equals(method)) {
* return new Object[]{String.format("Hello, %s!", args.checkString(0))};
* } else {
* throw new NoSuchMethodException();
* }
* }
* }
* </pre>
*/
public interface SimpleComponent {
/**
* The name the component should be made available as.
* <p/
* This is the name as seen in the <tt>component.list()</tt> in Lua, for
* example. You'll want to make this short and descriptive. The convention
* for component names is: all lowercase, underscores where necessary. Good
* component names are for example: disk_drive, furnace, crafting_table.
*
* @return the component's name.
*/
String getComponentName();
}

View File

@ -0,0 +1,24 @@
package li.cil.oc.common.asm;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import java.util.ArrayList;
// This class is used for adding simple components to the component network.
// It is triggered from a validate call, and executed in the next update tick.
public final class SimpleComponentTickHandler {
public static final ArrayList<Runnable> pendingOperations = new java.util.ArrayList<Runnable>();
public static final SimpleComponentTickHandler Instance = new SimpleComponentTickHandler();
@SubscribeEvent
public void onTick(TickEvent.ServerTickEvent e) {
synchronized (pendingOperations) {
for (Runnable runnable : pendingOperations) {
runnable.run();
}
pendingOperations.clear();
}
}
}

View File

@ -0,0 +1,21 @@
package li.cil.oc.common.asm.template;
import li.cil.oc.api.network.Environment;
import li.cil.oc.api.network.SimpleComponent;
import net.minecraft.nbt.NBTTagCompound;
// This interface defines the names to which existing or placeholders for
// existing methods will be moved. This allows transparent injection of our
// functionality, i.e. existing validate() etc. methods will be called as
// if we didn't inject our code.
public interface SimpleComponentImpl extends Environment, SimpleComponent {
void validate0();
void invalidate0();
void onChunkUnload0();
void readFromNBT0(NBTTagCompound nbt);
void writeToNBT0(NBTTagCompound nbt);
}

View File

@ -0,0 +1,85 @@
package li.cil.oc.common.asm.template;
import li.cil.oc.api.network.Message;
import li.cil.oc.api.network.Node;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
// This is a template implementation of methods injected into classes that are
// marked for component functionality. These methods will be copied into tile
// entities marked as simple components as necessary by the class transformer.
@SuppressWarnings("unused")
public abstract class SimpleEnvironment extends TileEntity implements SimpleComponentImpl {
@Override
public Node node() {
return StaticSimpleEnvironment.node(this);
}
@Override
public void onConnect(Node node) {
}
@Override
public void onDisconnect(Node node) {
}
@Override
public void onMessage(Message message) {
}
// These are always injected, after possibly existing versions have been
// renamed to the below variants from the SimpleComponentImpl interface.
// This allows transparent wrapping of already present implementations,
// instead of plain overwriting them.
@Override
public void validate() {
StaticSimpleEnvironment.validate(this);
}
@Override
public void invalidate() {
StaticSimpleEnvironment.invalidate(this);
}
@Override
public void onChunkUnload() {
StaticSimpleEnvironment.onChunkUnload(this);
}
@Override
public void readFromNBT(NBTTagCompound nbt) {
StaticSimpleEnvironment.readFromNBT(this, nbt);
}
@Override
public void writeToNBT(NBTTagCompound nbt) {
StaticSimpleEnvironment.writeToNBT(this, nbt);
}
// The following methods are only injected if their real versions do not
// exist in the class we're injecting into. Otherwise their real versions
// are renamed to these variations, which simply delegate to the parent.
// This way they are always guaranteed to be present, so we can simply call
// them through an interface, and need no runtime reflection.
public void validate0() {
super.validate();
}
public void invalidate0() {
super.invalidate();
}
public void onChunkUnload0() {
super.onChunkUnload();
}
public void readFromNBT0(NBTTagCompound nbt) {
super.readFromNBT(nbt);
}
public void writeToNBT0(NBTTagCompound nbt) {
super.writeToNBT(nbt);
}
}

View File

@ -0,0 +1,91 @@
package li.cil.oc.common.asm.template;
import cpw.mods.fml.common.FMLCommonHandler;
import li.cil.oc.api.Network;
import li.cil.oc.api.network.Environment;
import li.cil.oc.api.network.Node;
import li.cil.oc.api.network.Visibility;
import li.cil.oc.common.asm.SimpleComponentTickHandler;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import java.util.HashMap;
import java.util.Map;
// This class contains actual implementations of methods injected into tile
// entities marked as simple components using the SimpleComponent interface.
// They are called from the template methods, to keep the injected methods
// minimal, instruction wise, and avoid weird dependencies making the injection
// unnecessarily complicated.
public final class StaticSimpleEnvironment {
private StaticSimpleEnvironment() {
}
private static final Map<Environment, Node> nodes = new HashMap<Environment, Node>();
public static Node node(final SimpleComponentImpl self) {
// Save ourselves the lookup time in the hash map and avoid mixing in
// client side tile entities into the map when in single player.
if (FMLCommonHandler.instance().getEffectiveSide().isClient()) {
return null;
}
if (!nodes.containsKey(self)) {
final String name = self.getComponentName();
nodes.put(self, Network.
newNode(self, Visibility.Network).
withComponent(name).
create());
}
return nodes.get(self);
}
public static void validate(final SimpleComponentImpl self) {
self.validate0();
if (FMLCommonHandler.instance().getEffectiveSide().isServer()) {
synchronized (SimpleComponentTickHandler.pendingOperations) {
SimpleComponentTickHandler.pendingOperations.add(new Runnable() {
@Override
public void run() {
Network.joinOrCreateNetwork((TileEntity) self);
}
});
}
}
}
public static void invalidate(final SimpleComponentImpl self) {
self.invalidate0();
final Node node = node(self);
if (node != null) {
node.remove();
nodes.remove(self);
}
}
public static void onChunkUnload(final SimpleComponentImpl self) {
self.onChunkUnload0();
final Node node = node(self);
if (node != null) {
node.remove();
nodes.remove(self);
}
}
public static void readFromNBT(final SimpleComponentImpl self, NBTTagCompound nbt) {
self.readFromNBT0(nbt);
final Node node = node(self);
if (node != null) {
node.load(nbt.getCompoundTag("oc:node"));
}
}
public static void writeToNBT(final SimpleComponentImpl self, NBTTagCompound nbt) {
self.writeToNBT0(nbt);
final Node node = node(self);
if (node != null) {
final NBTTagCompound nodeNbt = new NBTTagCompound();
node.save(nodeNbt);
nbt.setTag("oc:node", nodeNbt);
}
}
}

View File

@ -62,7 +62,7 @@ object GuiHandler extends CommonGuiHandler {
else player.addChatMessage(new ChatComponentTranslation(Settings.namespace + "gui.Terminal.InvalidKey"))
}
else player.addChatMessage(new ChatComponentTranslation(Settings.namespace + "gui.Terminal.OutOfRange"))
case _ => null
case _ => player.addChatMessage(new ChatComponentTranslation(Settings.namespace + "gui.Terminal.OutOfRange"))
}
}
}

View File

@ -39,7 +39,6 @@ private[oc] class Proxy extends CommonProxy {
MinecraftForgeClient.registerItemRenderer(Items.multi, UpgradeRenderer)
OpenComputers.channel.register(client.PacketHandler)
}

View File

@ -4,6 +4,7 @@ import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.common.event._
import cpw.mods.fml.common.network.NetworkRegistry
import li.cil.oc._
import li.cil.oc.common.asm.SimpleComponentTickHandler
import li.cil.oc.server
import li.cil.oc.server.driver
import li.cil.oc.server.fs
@ -51,6 +52,7 @@ class Proxy {
driver.Registry.locked = true
FMLCommonHandler.instance().bus().register(EventHandler)
FMLCommonHandler.instance().bus().register(SimpleComponentTickHandler.Instance)
MinecraftForge.EVENT_BUS.register(Network)
MinecraftForge.EVENT_BUS.register(WirelessNetwork)
}

View File

@ -1,34 +1,150 @@
package li.cil.oc.common.asm
import cpw.mods.fml.common.Loader
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions
import java.util.logging.{Level, Logger}
import li.cil.oc.util.mods.StargateTech2
import net.minecraft.launchwrapper.IClassTransformer
import org.objectweb.asm.tree.ClassNode
import net.minecraft.launchwrapper.{LaunchClassLoader, IClassTransformer}
import org.objectweb.asm.tree._
import org.objectweb.asm.{ClassWriter, ClassReader}
import scala.collection.convert.WrapAsScala._
import net.minecraft.tileentity.TileEntity
@TransformerExclusions(Array("li.cil.oc.common.asm"))
class ClassTransformer extends IClassTransformer {
val loader = classOf[ClassTransformer].getClassLoader.asInstanceOf[LaunchClassLoader]
val log = Logger.getLogger("OpenComputers")
override def transform(name: String, transformedName: String, basicClass: Array[Byte]): Array[Byte] = {
if (name == "li.cil.oc.common.tileentity.Computer" || name == "li.cil.oc.common.tileentity.Rack") {
if (!Loader.isModLoaded("StargateTech2")) {
// Handled by @Optional.
return basicClass
}
if (StargateTech2.isAvailable) {
// All green, API version is new enough.
return basicClass
}
// Version of SGT2 is too old, abstract bus API doesn't exist.
val classNode = new ClassNode()
new ClassReader(basicClass).accept(classNode, 0)
classNode.interfaces.remove("stargatetech2/api/bus/IBusDevice")
val writer = new ClassWriter(ClassWriter.COMPUTE_MAXS)
classNode.accept(writer)
writer.toByteArray
return ensureStargateTechCompatibility(basicClass)
}
else basicClass
else if (basicClass != null
&& !name.startsWith( """net.minecraft.""")
&& !name.startsWith( """net.minecraftforge.""")
&& !name.startsWith( """li.cil.oc.common.asm.""")
&& !name.startsWith( """li.cil.oc.api.""")) {
val classNode = newClassNode(basicClass)
if (classNode.interfaces.contains("li/cil/oc/api/network/SimpleComponent")) {
try {
val transformedClass = injectEnvironmentImplementation(classNode, basicClass)
log.info(s"Successfully injected component logic into class $name.")
return transformedClass
} catch {
case e: Throwable =>
log.log(Level.WARNING, s"Failed injecting component logic into class $name.", e)
}
}
}
basicClass
}
def ensureStargateTechCompatibility(basicClass: Array[Byte]): Array[Byte] = {
if (!Loader.isModLoaded("StargateTech2")) {
// Handled by @Optional.
return basicClass
}
if (StargateTech2.isAvailable) {
// All green, API version is new enough.
return basicClass
}
// Version of SGT2 is too old, abstract bus API doesn't exist.
val classNode = newClassNode(basicClass)
classNode.interfaces.remove("stargatetech2/api/bus/IBusDevice")
writeClass(classNode)
}
def injectEnvironmentImplementation(classNode: ClassNode, basicClass: Array[Byte]): Array[Byte] = {
// TODO find actual implementations, i.e. descend into sub-classes until in a leaf, and transform those?
if (!isTileEntity(classNode)) {
throw new InjectionFailedException("Found SimpleComponent on something that isn't a tile entity, ignoring.")
}
val template = classNodeFor("li/cil/oc/common/asm/template/SimpleEnvironment")
log.fine("Injecting methods from Environment interface.")
def inject(methodName: String, signature: String, required: Boolean = false) {
def filter(method: MethodNode) = method.name == methodName && method.desc == signature
if (classNode.methods.exists(filter)) {
if (required) {
throw new InjectionFailedException(s"Could not inject method $methodName$signature because it was already present!")
}
}
else template.methods.find(filter) match {
case Some(method) => classNode.methods.add(method)
case _ => throw new AssertionError()
}
}
inject("node", "()Lli/cil/oc/api/network/Node;", required = true)
inject("onConnect", "(Lli/cil/oc/api/network/Node;)V")
inject("onDisconnect", "(Lli/cil/oc/api/network/Node;)V")
inject("onMessage", "(Lli/cil/oc/api/network/Message;)V")
log.fine("Injecting / wrapping overrides for required tile entity methods.")
def replace(methodName: String, methodNameSrg: String, desc: String) {
val mapper = FMLDeobfuscatingRemapper.INSTANCE
def filter(method: MethodNode) = {
val descDeObf = mapper.mapMethodDesc(method.desc)
val methodNameDeObf = mapper.mapMethodName(tileEntityName, method.name, method.desc)
val areSamePlain = method.name + descDeObf == methodName + desc
val areSameDeObf = methodNameDeObf + descDeObf == methodNameSrg + desc
areSamePlain || areSameDeObf
}
if (classNode.methods.exists(method => method.name == methodName + "0" && mapper.mapMethodDesc(method.desc) == desc)) {
throw new InjectionFailedException(s"Delegator method name ${methodName}0 is already in use.")
}
classNode.methods.find(filter) match {
case Some(method) =>
log.fine(s"Found original implementation of $methodName, wrapping.")
method.name = methodName + "0"
case _ =>
log.fine(s"No original implementation of $methodName, will inject override.")
template.methods.find(_.name == methodName + "0") match {
case Some(method) => classNode.methods.add(method)
case _ => throw new AssertionError(s"Couldn't find ${methodName}0 in template implementation.")
}
}
template.methods.find(filter) match {
case Some(method) => classNode.methods.add(method)
case _ => throw new AssertionError(s"Couldn't find $methodName in template implementation.")
}
}
replace("validate", "func_145829_t", "()V")
replace("invalidate", "func_145843_s", "()V")
replace("onChunkUnload", "func_76623_d", "()V")
replace("readFromNBT", "func_145839_a", "(Lnet/minecraft/nbt/NBTTagCompound;)V")
replace("writeToNBT", "func_145841_b", "(Lnet/minecraft/nbt/NBTTagCompound;)V")
log.fine("Injecting interface.")
classNode.interfaces.add("li/cil/oc/common/asm/template/SimpleComponentImpl")
writeClass(classNode, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES)
}
val tileEntityName = classOf[TileEntity].getName.replace('.', '/')
def isTileEntity(classNode: ClassNode): Boolean = {
classNode.name != "java/lang/Object" && (classNode.name == tileEntityName || isTileEntity(classNodeFor(classNode.superName)))
}
def classNodeFor(name: String) = newClassNode(loader.getClassBytes(name.replace('/', '.')))
def newClassNode(data: Array[Byte]) = {
val classNode = new ClassNode()
new ClassReader(data).accept(classNode, 0)
classNode
}
def writeClass(classNode: ClassNode, flags: Int = ClassWriter.COMPUTE_MAXS) = {
val writer = new ClassWriter(flags)
classNode.accept(writer)
writer.toByteArray
}
class InjectionFailedException(message: String) extends Exception(message)
}

View File

@ -14,7 +14,6 @@ import net.minecraft.entity.projectile.EntityArrow
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.AxisAlignedBB
import net.minecraftforge.common.util.ForgeDirection
import scala.Some
import scala.collection.mutable
import scala.language.postfixOps

View File

@ -40,7 +40,7 @@ class CompoundBlockDriver(val blocks: driver.Block*) extends driver.Block {
}
try world.getTileEntity(x, y, z) match {
case tileEntity: TileEntity =>
val map = ReflectionHelper.getPrivateValue[java.util.Map[Class[_], String], TileEntity](classOf[TileEntity], tileEntity, "classToNameMap", "field_70323_b")
val map = ReflectionHelper.getPrivateValue[java.util.Map[Class[_], String], TileEntity](classOf[TileEntity], tileEntity, "classToNameMap", "field_145853_j")
return map.get(tileEntity.getClass)
} catch {
case _: Throwable =>