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