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 | 20 | import org.objectweb.asm.tree.*; |
| 21 | 21 | |
| 22 | 22 | import com.mumfrey.liteloader.core.runtime.Obf; |
| 23 | +import com.mumfrey.liteloader.interfaces.FastIterableDeque; | |
| 23 | 24 | import com.mumfrey.liteloader.util.log.LiteLoaderLogger; |
| 24 | 25 | |
| 25 | 26 | /** |
| ... | ... | @@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; |
| 30 | 31 | * |
| 31 | 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 | 36 | private static final long serialVersionUID = 1L; |
| 36 | 37 | |
| 37 | 38 | private static final int MAX_UNCOLLECTED_CLASSES = 5000; |
| 38 | 39 | |
| 39 | 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 | 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 | 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 | 96 | */ |
| 55 | 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 | 108 | if (!type.isInterface()) |
| 58 | 109 | { |
| 59 | 110 | throw new IllegalArgumentException("HandlerList type argument must be an interface"); |
| 60 | 111 | } |
| 61 | 112 | |
| 62 | 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 | 119 | * |
| 68 | 120 | * @return |
| 69 | 121 | */ |
| 122 | + @Override | |
| 70 | 123 | public T all() |
| 71 | 124 | { |
| 72 | 125 | if (this.bakedHandler == null) |
| ... | ... | @@ -82,13 +135,14 @@ public class HandlerList<T> extends LinkedList<T> |
| 82 | 135 | */ |
| 83 | 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 | 139 | this.bakedHandler = classLoader.newHandler(this); |
| 87 | 140 | } |
| 88 | 141 | |
| 89 | 142 | /** |
| 90 | 143 | * Invalidate current baked list |
| 91 | 144 | */ |
| 145 | + @Override | |
| 92 | 146 | public void invalidate() |
| 93 | 147 | { |
| 94 | 148 | if (this.bakedHandler == null) |
| ... | ... | @@ -194,7 +248,16 @@ public class HandlerList<T> extends LinkedList<T> |
| 194 | 248 | @Override |
| 195 | 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 | 263 | /* (non-Javadoc) |
| ... | ... | @@ -413,6 +476,11 @@ public class HandlerList<T> extends LinkedList<T> |
| 413 | 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 | 484 | * Size of the handler list |
| 417 | 485 | */ |
| 418 | 486 | private int size; |
| ... | ... | @@ -421,11 +489,12 @@ public class HandlerList<T> extends LinkedList<T> |
| 421 | 489 | * @param type |
| 422 | 490 | * @param size |
| 423 | 491 | */ |
| 424 | - HandlerListClassLoader(Class<T> type) | |
| 492 | + HandlerListClassLoader(Class<T> type, ReturnLogicOp logicOp) | |
| 425 | 493 | { |
| 426 | 494 | super(new URL[0], Launch.classLoader); |
| 427 | 495 | this.type = type; |
| 428 | 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 | 575 | classNode.accept(classWriter); |
| 507 | 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 | 580 | // Delegate to ClassLoader's usual behaviour to load the class we just generated |
| 510 | 581 | return this.defineClass(name, bytes, 0, bytes.length); |
| 511 | 582 | } |
| ... | ... | @@ -614,6 +685,9 @@ public class HandlerList<T> extends LinkedList<T> |
| 614 | 685 | |
| 615 | 686 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 616 | 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 | 713 | |
| 640 | 714 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
| 641 | 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 | 752 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) |
| 676 | 753 | { |
| 677 | 754 | Type returnType = Type.getReturnType(method.desc); |
| 755 | + Type[] args = Type.getArgumentTypes(method.desc); | |
| 678 | 756 | |
| 679 | 757 | if (returnType.equals(Type.VOID_TYPE)) |
| 680 | 758 | { |
| 681 | - Type[] args = Type.getArgumentTypes(method.desc); | |
| 682 | 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 | 842 | * @param method |
| 702 | 843 | * @param args |
| 703 | 844 | */ |
| 704 | - private void invokeInterfaceMethod(MethodNode method, Type[] args) | |
| 845 | + private int invokeInterfaceMethod(MethodNode method, Type[] args) | |
| 705 | 846 | { |
| 706 | 847 | int argNumber = 1; |
| 707 | 848 | for (Type type : args) |
| ... | ... | @@ -711,6 +852,14 @@ public class HandlerList<T> extends LinkedList<T> |
| 711 | 852 | } |
| 712 | 853 | |
| 713 | 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