Commit 58a0b66325b564f439c5c896c38343cdb5723939
1 parent
3b64e194
support for boolean returns in HandlerList using logical operations between handlers
Showing
3 changed files
with
211 additions
and
18 deletions
java/common/com/mumfrey/liteloader/core/event/HandlerList.java
| @@ -20,6 +20,7 @@ import org.objectweb.asm.Type; | @@ -20,6 +20,7 @@ import org.objectweb.asm.Type; | ||
| 20 | import org.objectweb.asm.tree.*; | 20 | import org.objectweb.asm.tree.*; |
| 21 | 21 | ||
| 22 | import com.mumfrey.liteloader.core.runtime.Obf; | 22 | import com.mumfrey.liteloader.core.runtime.Obf; |
| 23 | +import com.mumfrey.liteloader.interfaces.FastIterableDeque; | ||
| 23 | import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | 24 | import com.mumfrey.liteloader.util.log.LiteLoaderLogger; |
| 24 | 25 | ||
| 25 | /** | 26 | /** |
| @@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | @@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | ||
| 30 | * | 31 | * |
| 31 | * @param <T> | 32 | * @param <T> |
| 32 | */ | 33 | */ |
| 33 | -public class HandlerList<T> extends LinkedList<T> | 34 | +public class HandlerList<T> extends LinkedList<T> implements FastIterableDeque<T> |
| 34 | { | 35 | { |
| 35 | private static final long serialVersionUID = 1L; | 36 | private static final long serialVersionUID = 1L; |
| 36 | 37 | ||
| 37 | private static final int MAX_UNCOLLECTED_CLASSES = 5000; | 38 | private static final int MAX_UNCOLLECTED_CLASSES = 5000; |
| 38 | 39 | ||
| 39 | private static int uncollectedHandlerLists = 0; | 40 | private static int uncollectedHandlerLists = 0; |
| 41 | + | ||
| 42 | + /** | ||
| 43 | + * Enum for logic operations supported between handlers which return bool | ||
| 44 | + */ | ||
| 45 | + public enum ReturnLogicOp | ||
| 46 | + { | ||
| 47 | + /** | ||
| 48 | + * Logical OR applied between handlers, return FALSE unless one or more handlers returns TRUE | ||
| 49 | + */ | ||
| 50 | + OR, | ||
| 51 | + | ||
| 52 | + /** | ||
| 53 | + * Logical OR, but with the difference than an EMPTY handler list will return TRUE | ||
| 54 | + */ | ||
| 55 | + OR_ASSUME_TRUE, | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * Logical AND, returns TRUE if the list is empty or if all handlers return TRUE | ||
| 59 | + */ | ||
| 60 | + AND, | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * Logical AND, returns FALSE at the first handler to return FALSE and doesn't process any further handlers | ||
| 64 | + */ | ||
| 65 | + AND_BREAK_ON_FALSE; | ||
| 66 | + | ||
| 67 | + boolean isOr() | ||
| 68 | + { | ||
| 69 | + return this == OR || this == OR_ASSUME_TRUE; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + boolean assumeTrue() | ||
| 73 | + { | ||
| 74 | + return this == OR_ASSUME_TRUE; | ||
| 75 | + } | ||
| 76 | + } | ||
| 40 | 77 | ||
| 41 | /** | 78 | /** |
| 42 | * Type of the interface for objects in this handler list | 79 | * Type of the interface for objects in this handler list |
| 43 | */ | 80 | */ |
| 44 | - private Class<T> type; | 81 | + private final Class<T> type; |
| 82 | + | ||
| 83 | + /** | ||
| 84 | + * | ||
| 85 | + */ | ||
| 86 | + private final ReturnLogicOp logicOp; | ||
| 45 | 87 | ||
| 46 | /** | 88 | /** |
| 47 | * Current baked handler list, we cook them at gas mark 5 for 30 minutes in a disposable classloader whic | 89 | * Current baked handler list, we cook them at gas mark 5 for 30 minutes in a disposable classloader whic |
| @@ -54,12 +96,22 @@ public class HandlerList<T> extends LinkedList<T> | @@ -54,12 +96,22 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 54 | */ | 96 | */ |
| 55 | public HandlerList(Class<T> type) | 97 | public HandlerList(Class<T> type) |
| 56 | { | 98 | { |
| 99 | + this(type, ReturnLogicOp.AND_BREAK_ON_FALSE); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + /** | ||
| 103 | + * @param type | ||
| 104 | + * @param logicOp Logical operation to apply to interface methods which return boolean | ||
| 105 | + */ | ||
| 106 | + public HandlerList(Class<T> type, ReturnLogicOp logicOp) | ||
| 107 | + { | ||
| 57 | if (!type.isInterface()) | 108 | if (!type.isInterface()) |
| 58 | { | 109 | { |
| 59 | throw new IllegalArgumentException("HandlerList type argument must be an interface"); | 110 | throw new IllegalArgumentException("HandlerList type argument must be an interface"); |
| 60 | } | 111 | } |
| 61 | 112 | ||
| 62 | this.type = type; | 113 | this.type = type; |
| 114 | + this.logicOp = logicOp; | ||
| 63 | } | 115 | } |
| 64 | 116 | ||
| 65 | /** | 117 | /** |
| @@ -67,6 +119,7 @@ public class HandlerList<T> extends LinkedList<T> | @@ -67,6 +119,7 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 67 | * | 119 | * |
| 68 | * @return | 120 | * @return |
| 69 | */ | 121 | */ |
| 122 | + @Override | ||
| 70 | public T all() | 123 | public T all() |
| 71 | { | 124 | { |
| 72 | if (this.bakedHandler == null) | 125 | if (this.bakedHandler == null) |
| @@ -82,13 +135,14 @@ public class HandlerList<T> extends LinkedList<T> | @@ -82,13 +135,14 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 82 | */ | 135 | */ |
| 83 | protected void bake() | 136 | protected void bake() |
| 84 | { | 137 | { |
| 85 | - HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type); | 138 | + HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type, this.logicOp); |
| 86 | this.bakedHandler = classLoader.newHandler(this); | 139 | this.bakedHandler = classLoader.newHandler(this); |
| 87 | } | 140 | } |
| 88 | 141 | ||
| 89 | /** | 142 | /** |
| 90 | * Invalidate current baked list | 143 | * Invalidate current baked list |
| 91 | */ | 144 | */ |
| 145 | + @Override | ||
| 92 | public void invalidate() | 146 | public void invalidate() |
| 93 | { | 147 | { |
| 94 | if (this.bakedHandler == null) | 148 | if (this.bakedHandler == null) |
| @@ -194,7 +248,16 @@ public class HandlerList<T> extends LinkedList<T> | @@ -194,7 +248,16 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 194 | @Override | 248 | @Override |
| 195 | public boolean addAll(Collection<? extends T> listeners) | 249 | public boolean addAll(Collection<? extends T> listeners) |
| 196 | { | 250 | { |
| 197 | - throw new UnsupportedOperationException("'addAll' is not supported for HandlerList"); | 251 | + for (T listener : listeners) |
| 252 | + { | ||
| 253 | + if (!this.contains(listener)) | ||
| 254 | + { | ||
| 255 | + super.add(listener); | ||
| 256 | + } | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + this.invalidate(); | ||
| 260 | + return true; | ||
| 198 | } | 261 | } |
| 199 | 262 | ||
| 200 | /* (non-Javadoc) | 263 | /* (non-Javadoc) |
| @@ -413,6 +476,11 @@ public class HandlerList<T> extends LinkedList<T> | @@ -413,6 +476,11 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 413 | private final String typeRef; | 476 | private final String typeRef; |
| 414 | 477 | ||
| 415 | /** | 478 | /** |
| 479 | + * Logic operation to apply when running a callback with a boolean | ||
| 480 | + */ | ||
| 481 | + private final ReturnLogicOp logicOp; | ||
| 482 | + | ||
| 483 | + /** | ||
| 416 | * Size of the handler list | 484 | * Size of the handler list |
| 417 | */ | 485 | */ |
| 418 | private int size; | 486 | private int size; |
| @@ -421,11 +489,12 @@ public class HandlerList<T> extends LinkedList<T> | @@ -421,11 +489,12 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 421 | * @param type | 489 | * @param type |
| 422 | * @param size | 490 | * @param size |
| 423 | */ | 491 | */ |
| 424 | - HandlerListClassLoader(Class<T> type) | 492 | + HandlerListClassLoader(Class<T> type, ReturnLogicOp logicOp) |
| 425 | { | 493 | { |
| 426 | super(new URL[0], Launch.classLoader); | 494 | super(new URL[0], Launch.classLoader); |
| 427 | this.type = type; | 495 | this.type = type; |
| 428 | this.typeRef = type.getName().replace('.', '/'); | 496 | this.typeRef = type.getName().replace('.', '/'); |
| 497 | + this.logicOp = logicOp; | ||
| 429 | } | 498 | } |
| 430 | 499 | ||
| 431 | /** | 500 | /** |
| @@ -506,6 +575,8 @@ public class HandlerList<T> extends LinkedList<T> | @@ -506,6 +575,8 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 506 | classNode.accept(classWriter); | 575 | classNode.accept(classWriter); |
| 507 | bytes = classWriter.toByteArray(); | 576 | bytes = classWriter.toByteArray(); |
| 508 | 577 | ||
| 578 | +// classNode.accept(new org.objectweb.asm.util.CheckClassAdapter(new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES))); | ||
| 579 | + | ||
| 509 | // Delegate to ClassLoader's usual behaviour to load the class we just generated | 580 | // Delegate to ClassLoader's usual behaviour to load the class we just generated |
| 510 | return this.defineClass(name, bytes, 0, bytes.length); | 581 | return this.defineClass(name, bytes, 0, bytes.length); |
| 511 | } | 582 | } |
| @@ -614,6 +685,9 @@ public class HandlerList<T> extends LinkedList<T> | @@ -614,6 +685,9 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 614 | 685 | ||
| 615 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | 686 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 616 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); | 687 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); |
| 688 | + | ||
| 689 | + method.maxStack = 1; | ||
| 690 | + method.maxLocals = 1; | ||
| 617 | } | 691 | } |
| 618 | 692 | ||
| 619 | /** | 693 | /** |
| @@ -639,6 +713,9 @@ public class HandlerList<T> extends LinkedList<T> | @@ -639,6 +713,9 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 639 | 713 | ||
| 640 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | 714 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 641 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); | 715 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); |
| 716 | + | ||
| 717 | + method.maxStack = 3; | ||
| 718 | + method.maxLocals = 2; | ||
| 642 | } | 719 | } |
| 643 | 720 | ||
| 644 | /** | 721 | /** |
| @@ -675,24 +752,88 @@ public class HandlerList<T> extends LinkedList<T> | @@ -675,24 +752,88 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 675 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) | 752 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) |
| 676 | { | 753 | { |
| 677 | Type returnType = Type.getReturnType(method.desc); | 754 | Type returnType = Type.getReturnType(method.desc); |
| 755 | + Type[] args = Type.getArgumentTypes(method.desc); | ||
| 678 | 756 | ||
| 679 | if (returnType.equals(Type.VOID_TYPE)) | 757 | if (returnType.equals(Type.VOID_TYPE)) |
| 680 | { | 758 | { |
| 681 | - Type[] args = Type.getArgumentTypes(method.desc); | ||
| 682 | method.access = Opcodes.ACC_PUBLIC; | 759 | method.access = Opcodes.ACC_PUBLIC; |
| 760 | + this.populateVoidInvokationChain(classNode, method, args); | ||
| 761 | + } | ||
| 762 | + else if (returnType.equals(Type.BOOLEAN_TYPE)) | ||
| 763 | + { | ||
| 764 | + method.access = Opcodes.ACC_PUBLIC; | ||
| 765 | + this.populateBooleanInvokationChain(classNode, method, args); | ||
| 766 | + } | ||
| 767 | + } | ||
| 768 | + | ||
| 769 | + /** | ||
| 770 | + * @param classNode | ||
| 771 | + * @param method | ||
| 772 | + * @param args | ||
| 773 | + */ | ||
| 774 | + private void populateVoidInvokationChain(ClassNode classNode, MethodNode method, Type[] args) | ||
| 775 | + { | ||
| 776 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
| 777 | + { | ||
| 778 | + this.invokeHandler(handlerIndex, classNode, method, args); | ||
| 779 | + } | ||
| 780 | + | ||
| 781 | + method.instructions.add(new InsnNode(Opcodes.RETURN)); | ||
| 782 | + | ||
| 783 | + method.maxLocals = args.length + 1; | ||
| 784 | + method.maxStack = args.length + 1; | ||
| 785 | + } | ||
| 786 | + | ||
| 787 | + /** | ||
| 788 | + * @param classNode | ||
| 789 | + * @param method | ||
| 790 | + * @param args | ||
| 791 | + */ | ||
| 792 | + private void populateBooleanInvokationChain(ClassNode classNode, MethodNode method, Type[] args) | ||
| 793 | + { | ||
| 794 | + boolean isOrOperation = this.logicOp.isOr(); | ||
| 795 | + boolean breakOnFalse = this.logicOp == ReturnLogicOp.AND_BREAK_ON_FALSE; | ||
| 796 | + int initialValue = isOrOperation && (!this.logicOp.assumeTrue() || this.size > 0) ? Opcodes.ICONST_0 : Opcodes.ICONST_1; | ||
| 797 | + int localIndex = this.getFirstLocalIndex(args); | ||
| 798 | + | ||
| 799 | + method.instructions.add(new InsnNode(initialValue)); | ||
| 800 | + method.instructions.add(new VarInsnNode(Opcodes.ISTORE, localIndex)); | ||
| 801 | + | ||
| 802 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
| 803 | + { | ||
| 804 | + this.invokeHandler(handlerIndex, classNode, method, args); // invoke the method, this will leave the return value on the stack | ||
| 683 | 805 | ||
| 684 | - for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
| 685 | - { | ||
| 686 | - LabelNode lineNumberLabel = new LabelNode(new Label()); | ||
| 687 | - method.instructions.add(lineNumberLabel); | ||
| 688 | - method.instructions.add(new LineNumberNode(100 + handlerIndex, lineNumberLabel)); | ||
| 689 | - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | ||
| 690 | - method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | ||
| 691 | - this.invokeInterfaceMethod(method, args); | ||
| 692 | - } | 806 | + int jumpCondition = isOrOperation ? Opcodes.IFEQ : Opcodes.IFNE; // jump if zero for OR, jump if one for AND |
| 807 | + int semaphore = isOrOperation ? Opcodes.ICONST_1 : Opcodes.ICONST_0; // will push TRUE for OR, will push FALSE for AND | ||
| 693 | 808 | ||
| 694 | - method.instructions.add(new InsnNode(Opcodes.RETURN)); | ||
| 695 | - } | 809 | + LabelNode lbl = new LabelNode(); |
| 810 | + method.instructions.add(new JumpInsnNode(jumpCondition, lbl)); // jump over the set/return based on the condition | ||
| 811 | + method.instructions.add(new InsnNode(semaphore)); // push TRUE or FALSE onto the stack | ||
| 812 | + method.instructions.add(breakOnFalse ? new InsnNode(Opcodes.IRETURN) : new VarInsnNode(Opcodes.ISTORE, localIndex)); // set local or return | ||
| 813 | + method.instructions.add(lbl); // jump here | ||
| 814 | + } | ||
| 815 | + | ||
| 816 | + method.instructions.add(new VarInsnNode(Opcodes.ILOAD, localIndex)); | ||
| 817 | + method.instructions.add(new InsnNode(Opcodes.IRETURN)); | ||
| 818 | + | ||
| 819 | + method.maxLocals = args.length + 2; | ||
| 820 | + method.maxStack = args.length + 1; | ||
| 821 | + } | ||
| 822 | + | ||
| 823 | + /** | ||
| 824 | + * @param handlerIndex | ||
| 825 | + * @param classNode | ||
| 826 | + * @param method | ||
| 827 | + * @param args | ||
| 828 | + */ | ||
| 829 | + private int invokeHandler(int handlerIndex, ClassNode classNode, MethodNode method, Type[] args) | ||
| 830 | + { | ||
| 831 | + LabelNode lineNumberLabel = new LabelNode(new Label()); | ||
| 832 | + method.instructions.add(lineNumberLabel); | ||
| 833 | + method.instructions.add(new LineNumberNode(100 + handlerIndex, lineNumberLabel)); | ||
| 834 | + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | ||
| 835 | + method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | ||
| 836 | + return this.invokeInterfaceMethod(method, args); | ||
| 696 | } | 837 | } |
| 697 | 838 | ||
| 698 | /** | 839 | /** |
| @@ -701,7 +842,7 @@ public class HandlerList<T> extends LinkedList<T> | @@ -701,7 +842,7 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 701 | * @param method | 842 | * @param method |
| 702 | * @param args | 843 | * @param args |
| 703 | */ | 844 | */ |
| 704 | - private void invokeInterfaceMethod(MethodNode method, Type[] args) | 845 | + private int invokeInterfaceMethod(MethodNode method, Type[] args) |
| 705 | { | 846 | { |
| 706 | int argNumber = 1; | 847 | int argNumber = 1; |
| 707 | for (Type type : args) | 848 | for (Type type : args) |
| @@ -711,6 +852,14 @@ public class HandlerList<T> extends LinkedList<T> | @@ -711,6 +852,14 @@ public class HandlerList<T> extends LinkedList<T> | ||
| 711 | } | 852 | } |
| 712 | 853 | ||
| 713 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, this.typeRef, method.name, method.desc, true)); | 854 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, this.typeRef, method.name, method.desc, true)); |
| 855 | + return argNumber; | ||
| 856 | + } | ||
| 857 | + | ||
| 858 | + private int getFirstLocalIndex(Type[] args) | ||
| 859 | + { | ||
| 860 | + int argNumber = 1; | ||
| 861 | + for (Type type : args) argNumber += type.getSize(); | ||
| 862 | + return argNumber; | ||
| 714 | } | 863 | } |
| 715 | 864 | ||
| 716 | /** | 865 | /** |
java/common/com/mumfrey/liteloader/interfaces/FastIterable.java
0 → 100644
| 1 | +package com.mumfrey.liteloader.interfaces; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * Interface for objects which can return a baked list view of their list contents | ||
| 5 | + * | ||
| 6 | + * @author Adam Mummery-Smith | ||
| 7 | + * | ||
| 8 | + * @param <T> | ||
| 9 | + */ | ||
| 10 | +public interface FastIterable<T> | ||
| 11 | +{ | ||
| 12 | + /** | ||
| 13 | + * Add an entry to the iterable | ||
| 14 | + * | ||
| 15 | + * @param entry | ||
| 16 | + */ | ||
| 17 | + public boolean add(T entry); | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * Return the baked view of all entries | ||
| 21 | + * | ||
| 22 | + * @return | ||
| 23 | + */ | ||
| 24 | + public T all(); | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * Invalidate (force rebake of) the baked entry list | ||
| 28 | + */ | ||
| 29 | + public void invalidate(); | ||
| 30 | +} |
java/common/com/mumfrey/liteloader/interfaces/FastIterableDeque.java
0 → 100644