From b2cbd9c0849ffc951a1eb285827d02b6648dfc85 Mon Sep 17 00:00:00 2001 From: nathan Date: Wed, 27 Jan 2016 12:54:57 +0100 Subject: [PATCH] Moved ConcurrentSet entries/etc into subscription -> BEST performance for subscription iteration w/o GC --- .../messagebus/subscription/Subscription.java | 191 +++--- .../subscription/SubscriptionManager.java | 611 +++++++++--------- 2 files changed, 402 insertions(+), 400 deletions(-) diff --git a/src/dorkbox/util/messagebus/subscription/Subscription.java b/src/dorkbox/util/messagebus/subscription/Subscription.java index bf5f7e4..f2fdca1 100644 --- a/src/dorkbox/util/messagebus/subscription/Subscription.java +++ b/src/dorkbox/util/messagebus/subscription/Subscription.java @@ -39,12 +39,12 @@ package dorkbox.util.messagebus.subscription; import com.esotericsoftware.kryo.util.IdentityMap; import com.esotericsoftware.reflectasm.MethodAccess; +import dorkbox.util.messagebus.common.Entry; import dorkbox.util.messagebus.common.MessageHandler; import dorkbox.util.messagebus.dispatch.IHandlerInvocation; import dorkbox.util.messagebus.dispatch.ReflectiveHandlerInvocation; import dorkbox.util.messagebus.dispatch.SynchronizedHandlerInvocation; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -65,8 +65,6 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; */ public final class Subscription { - private static final int GROW_SIZE = 8; - private static final AtomicInteger ID_COUNTER = new AtomicInteger(); public final int ID = ID_COUNTER.getAndIncrement(); @@ -78,17 +76,18 @@ class Subscription { private final MessageHandler handler; private final IHandlerInvocation invocation; - // NOTE: this is still inside the single-writer! can use the same techniques as subscription manager (for thread safe publication) - private int firstFreeSpot = 0; // only touched by a single thread - private volatile Object[] listeners = new Object[GROW_SIZE]; // only modified by a single thread - - private final IdentityMap listenerMap = new IdentityMap<>(GROW_SIZE); - // Recommended for best performance while adhering to the "single writer principle". Must be static-final - private static final AtomicReferenceFieldUpdater listenersREF = + private static final AtomicReferenceFieldUpdater headREF = AtomicReferenceFieldUpdater.newUpdater(Subscription.class, - Object[].class, - "listeners"); + Entry.class, + "head"); + + + // This is only touched by a single thread! + private final IdentityMap entries; // maintain a map of entries for FAST lookup during unsubscribe. + + // this is still inside the single-writer, and can use the same techniques as subscription manager (for thread safe publication) + public volatile Entry head; // reference to the first element public @@ -102,6 +101,21 @@ class Subscription { } this.invocation = invocation; + + entries = new IdentityMap<>(32, SubscriptionManager.LOAD_FACTOR); + + + if (handler.acceptsSubtypes()) { + // keep a list of "super-class" messages that access this. This is updated by multiple threads. This is so we know WHAT + // super-subscriptions to clear when we sub/unsub + + } + } + + // called on shutdown for GC purposes + public void clear() { + this.entries.clear(); + this.head.clear(); } // only used in unit tests to verify that the subscription manager is working correctly @@ -118,31 +132,14 @@ class Subscription { public void subscribe(final Object listener) { // single writer principle! + Entry head = headREF.get(this); - Object[] localListeners = listenersREF.get(this); + if (!entries.containsKey(listener)) { + head = new Entry(listener, head); - final int length = localListeners.length; - int spotToPlace = firstFreeSpot; - - while (true) { - if (spotToPlace >= length) { - // if we couldn't find a place to put the listener, grow the array, but it is never shrunk - localListeners = Arrays.copyOf(localListeners, length + GROW_SIZE, Object[].class); - break; - } - - if (localListeners[spotToPlace] == null) { - break; - } - spotToPlace++; + entries.put(listener, head); + headREF.lazySet(this, head); } - - listenerMap.put(listener, spotToPlace); - localListeners[spotToPlace] = listener; - - // mark this spot as taken, so the next subscribe starts out a little ahead - firstFreeSpot = spotToPlace + 1; - listenersREF.lazySet(this, localListeners); } /** @@ -150,31 +147,28 @@ class Subscription { */ public boolean unsubscribe(final Object listener) { - // single writer principle! - - final Integer integer = listenerMap.remove(listener); - Object[] localListeners = listenersREF.get(this); - - if (integer != null) { - final int index = integer; - firstFreeSpot = index; - localListeners[index] = null; - listenersREF.lazySet(this, localListeners); - return true; + Entry entry = entries.get(listener); + if (entry == null || entry.getValue() == null) { + // fast exit + return false; } else { - for (int i = 0; i < localListeners.length; i++) { - if (localListeners[i] == listener) { - firstFreeSpot = i; - localListeners[i] = null; - listenersREF.lazySet(this, localListeners); - return true; - } - } - } + // single writer principle! + Entry head = headREF.get(this); - firstFreeSpot = 0; - return false; + if (entry != head) { + entry.remove(); + } + else { + // if it was second, now it's first + head = head.next(); + //oldHead.clear(); // optimize for GC not possible because of potentially running iterators + } + + this.entries.remove(listener); + headREF.lazySet(this, head); + return true; + } } /** @@ -182,16 +176,7 @@ class Subscription { */ public int size() { - // since this is ONLY used in unit tests, we count how many are non-null - - int count = 0; - for (int i = 0; i < listeners.length; i++) { - if (listeners[i] != null) { - count++; - } - } - - return count; + return this.entries.size; } public @@ -200,62 +185,46 @@ class Subscription { final int handleIndex = this.handler.getMethodIndex(); final IHandlerInvocation invocation = this.invocation; + Entry current = headREF.get(this); Object listener; - final Object[] localListeners = listenersREF.get(this); - for (int i = 0; i < localListeners.length; i++) { - listener = localListeners[i]; - if (listener != null) { - invocation.invoke(listener, handler, handleIndex, message); - } + while (current != null) { + listener = current.getValue(); + current = current.next(); + + invocation.invoke(listener, handler, handleIndex, message); } } public void publish(final Object message1, final Object message2) throws Throwable { -// final MethodAccess handler = this.handler.getHandler(); -// final int handleIndex = this.handler.getMethodIndex(); -// final IHandlerInvocation invocation = this.invocation; -// -// Iterator iterator; -// Object listener; -// -// for (iterator = this.listeners.iterator(); iterator.hasNext(); ) { -// listener = iterator.next(); -// -// invocation.invoke(listener, handler, handleIndex, message1, message2); -// } + final MethodAccess handler = this.handler.getHandler(); + final int handleIndex = this.handler.getMethodIndex(); + final IHandlerInvocation invocation = this.invocation; + + Entry current = headREF.get(this); + Object listener; + while (current != null) { + listener = current.getValue(); + current = current.next(); + + invocation.invoke(listener, handler, handleIndex, message1, message2); + } } public void publish(final Object message1, final Object message2, final Object message3) throws Throwable { -// final MethodAccess handler = this.handler.getHandler(); -// final int handleIndex = this.handler.getMethodIndex(); -// final IHandlerInvocation invocation = this.invocation; -// -// Iterator iterator; -// Object listener; -// -// for (iterator = this.listeners.iterator(); iterator.hasNext(); ) { -// listener = iterator.next(); -// -// invocation.invoke(listener, handler, handleIndex, message1, message2, message3); -// } - } + final MethodAccess handler = this.handler.getHandler(); + final int handleIndex = this.handler.getMethodIndex(); + final IHandlerInvocation invocation = this.invocation; - public - void publish(final Object... messages) throws Throwable { -// final MethodAccess handler = this.handler.getHandler(); -// final int handleIndex = this.handler.getMethodIndex(); -// final IHandlerInvocation invocation = this.invocation; -// -// Iterator iterator; -// Object listener; -// -// for (iterator = this.listeners.iterator(); iterator.hasNext(); ) { -// listener = iterator.next(); -// -// invocation.invoke(listener, handler, handleIndex, messages); -// } + Entry current = headREF.get(this); + Object listener; + while (current != null) { + listener = current.getValue(); + current = current.next(); + + invocation.invoke(listener, handler, handleIndex, message1, message2, message3); + } } diff --git a/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java b/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java index fd6c0b6..786914c 100644 --- a/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java +++ b/src/dorkbox/util/messagebus/subscription/SubscriptionManager.java @@ -16,18 +16,16 @@ package dorkbox.util.messagebus.subscription; import com.esotericsoftware.kryo.util.IdentityMap; -import dorkbox.util.messagebus.common.HashMapTree; +import dorkbox.util.messagebus.common.ClassTree; import dorkbox.util.messagebus.common.MessageHandler; +import dorkbox.util.messagebus.common.MultiClass; import dorkbox.util.messagebus.error.ErrorHandlingSupport; import dorkbox.util.messagebus.utils.ClassUtils; -import dorkbox.util.messagebus.utils.ReflectionUtils; import dorkbox.util.messagebus.utils.SubscriptionUtils; import dorkbox.util.messagebus.utils.VarArgUtils; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; -import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; /** @@ -42,6 +40,7 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; * @author dorkbox, llc * Date: 2/2/15 */ +@SuppressWarnings("unchecked") public final class SubscriptionManager { public static final float LOAD_FACTOR = 0.8F; @@ -65,6 +64,7 @@ class SubscriptionManager { // all subscriptions of a message type. private volatile IdentityMap, Subscription[]> subsSingle; + private volatile IdentityMap subsMulti; // keeps track of all subscriptions of the super classes of a message type. private volatile IdentityMap, Subscription[]> subsSuperSingle; @@ -77,7 +77,7 @@ class SubscriptionManager { // In order to force the "Single writer principle" on subscribe & unsubscribe, they are within WRITE LOCKS. They could be dispatched // to another thread, however we do NOT want them asynchronous - as publish() should ALWAYS succeed if a correct subscribe() is - // called before. + // called before. A WriteLock doesn't perform any better here than synchronized does. private final Object singleWriterLock = new Object(); @@ -88,7 +88,7 @@ class SubscriptionManager { private final SubscriptionUtils subUtils; private final VarArgUtils varArgUtils; - private final HashMapTree, ArrayList> subscriptionsPerMessageMulti; + private final ClassTree> classTree; // shortcut publication if we know there is no possibility of varArg (ie: a method that has an array as arguments) private final AtomicBoolean varArgPossibility = new AtomicBoolean(false); @@ -102,6 +102,12 @@ class SubscriptionManager { IdentityMap.class, "subsSingle"); + private static final AtomicReferenceFieldUpdater subsMultiREF = + AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class, + IdentityMap.class, + "subsMulti"); + + private static final AtomicReferenceFieldUpdater subsSuperSingleREF = AtomicReferenceFieldUpdater.newUpdater(SubscriptionManager.class, IdentityMap.class, @@ -131,14 +137,16 @@ class SubscriptionManager { nonListeners = new IdentityMap, Boolean>(16, LOAD_FACTOR); subsPerListener = new IdentityMap<>(32, LOAD_FACTOR); subsSingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); + subsMulti = new IdentityMap(32, LOAD_FACTOR); + + subsSuperSingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); subsVaritySingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); subsSuperVaritySingle = new IdentityMap, Subscription[]>(32, LOAD_FACTOR); - - this.subscriptionsPerMessageMulti = new HashMapTree, ArrayList>(); + this.classTree = new ClassTree>(); this.subUtils = new SubscriptionUtils(classUtils, LOAD_FACTOR, numberOfThreads); @@ -151,6 +159,22 @@ class SubscriptionManager { public void shutdown() { + + // explicitly clear out the subscriptions + final IdentityMap.Entries, Subscription[]> entries = subsPerListener.entries(); + for (IdentityMap.Entry, Subscription[]> entry : entries) { + final Subscription[] subscriptions = entry.value; + if (subscriptions != null) { + Subscription subscription; + + for (int i = 0; i < subscriptions.length; i++) { + subscription = subscriptions[i]; + subscription.clear(); + } + } + } + + this.nonListeners.clear(); this.subsPerListener.clear(); @@ -160,8 +184,7 @@ class SubscriptionManager { this.subsVaritySingle.clear(); this.subsSuperVaritySingle.clear(); - - this.subscriptionsPerMessageMulti.clear(); + this.classTree.clear(); this.classUtils.shutdown(); } @@ -174,38 +197,6 @@ class SubscriptionManager { // section. Because of this, we can have unlimited reader threads all going at the same time, without contention (which is our // use-case 99% of the time) synchronized (singleWriterLock) { - - // when subscribing, this is a GREAT opportunity to figure out the classes/objects loaded -- their hierarchy, AND generate UUIDs - // for each CLASS that can be accessed. This then lets us lookup a UUID for each object that comes in -- if an ID is found (for - // any part of it's object hierarchy) -- it means that we have that listeners for that object. this is MUCH faster checking if - // we have subscriptions first (and failing). - // - // so during subscribe we can check "getUUID for all parameter.class accessed by this listener" -> then during publish "lookup - // UUID of incoming message.class" (+ it's super classes, if necessary) -> then check if UUID exists. If yes, then we know there - // are subs. if no - then it's a dead message. - // - // This lets us accomplish TWO things - // 1) be able quickly determine if there are dead messages - // 2) be able to create "multi-class" UUIDs, when two+ classes are represented (always) by the same UUID, by a clever mixing of - // the classes individual UUIDs. - // - // The generation of UUIDs happens ONLY during subscribe, and during publish they are looked up. This UUID can be a simple - // AtomicInteger that starts a MIN_VALUE and count's up. - - - // note: we can do PRE-STARTUP instrumentation (ie, BEFORE any classes are loaded by the classloader) and inject the UUID into - // every object (as a public static final field), then use reflection to look up this value. It would go something like this: - // 1) scan every class for annotations that match - // 2) for each method that has our annotation -- get the list of classes + hierarchy that are the parameters for the method - // 3) inject the UUID field into each class object that #2 returns, only if it doesn't already exist. use invalid field names - // (ie: start with numbers or ? or ^ or something - // - // then during SUB/UNSUB/PUB, we use this UUID for everything (and we can have multi-UUID lookups for the 'multi-arg' thing). - // If there is no UUID, then we just abort the SUB/UNSUB or send a deadmessage - - - - final IdentityMap, Boolean> nonListeners = this.nonListeners; if (nonListeners.containsKey(listenerClass)) { // early reject of known classes that do not define message handlers @@ -231,7 +222,9 @@ class SubscriptionManager { subscriptions = new Subscription[handlersSize]; // access a snapshot of the subscriptions (single-writer-principle) - final IdentityMap, Subscription[]> localSubs = subsSingleREF.get(this); + final IdentityMap, Subscription[]> singleSubs = subsSingleREF.get(this); +// final IdentityMap multiSubs = subsMultiREF.get(this); + // final IdentityMap, Subscription[]> localSuperSubs = subsSuperSingleREF.get(this); // final IdentityMap, Subscription[]> localVaritySubs = subsVaritySingleREF.get(this); // final IdentityMap, Subscription[]> localSuperVaritySubs = subsSuperVaritySingleREF.get(this); @@ -242,24 +235,73 @@ class SubscriptionManager { Class[] messageHandlerTypes; Class handlerType; + + // Prepare all of the subscriptions for (int i = 0; i < handlersSize; i++) { // THE HANDLER IS THE SAME FOR ALL SUBSCRIPTIONS OF THE SAME TYPE! messageHandler = messageHandlers[i]; // is this handler able to accept var args? - if (messageHandler.getVarArgClass() != null) { - varArgPossibility.lazySet(true); - } +// if (messageHandler.getVarArgClass() != null) { +// varArgPossibility.lazySet(true); +// } // now create a list of subscriptions for this specific handlerType (but don't add anything yet). // we only store things based on the FIRST type (for lookup) then parse the rest of the types during publication messageHandlerTypes = messageHandler.getHandledMessages(); - handlerType = messageHandlerTypes[0]; +// final int handlerSize = messageHandlerTypes.length; +// switch (handlerSize) { +// case 0: { +// // if a publisher publishes VOID, it calls a method with 0 parameters (that's been subscribed) +// // This is the SAME THING as having Void as a parameter!! +// handlerType = Void.class; +// +// if (!singleSubs.containsKey(handlerType)) { +// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added +// singleSubs.put(handlerType, SUBSCRIPTIONS); +// } +// break; +// } +// case 1: { + handlerType = messageHandlerTypes[0]; - if (!localSubs.containsKey(handlerType)) { - // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added - localSubs.put(handlerType, SUBSCRIPTIONS); - } + if (!singleSubs.containsKey(handlerType)) { + // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added + singleSubs.put(handlerType, SUBSCRIPTIONS); + } +// break; +// } +// case 2: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], +// messageHandlerTypes[1]); +// +// if (!multiSubs.containsKey(multiClass)) { +// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added +// multiSubs.put(multiClass, SUBSCRIPTIONS); +// } +// break; +// } +// case 3: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], +// messageHandlerTypes[1], +// messageHandlerTypes[2]); +// +// if (!multiSubs.containsKey(multiClass)) { +// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added +// multiSubs.put(multiClass, SUBSCRIPTIONS); +// } +// break; +// } +// default: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes); +// +// if (!multiSubs.containsKey(multiClass)) { +// // this is copied to a larger array if necessary, but needs to be SOMETHING before subsPerListener is added +// multiSubs.put(multiClass, SUBSCRIPTIONS); +// } +// break; +// } +// } // create the subscription. This can be thrown away if the subscription succeeds in another thread subscription = new Subscription(listenerClass, messageHandler); @@ -272,7 +314,7 @@ class SubscriptionManager { subsPerListener.put(listenerClass, subscriptions); - // we can now safely add for publication AND subscribe since the data structures are consistent + // add for publication AND subscribe since the data structures are consistent for (int i = 0; i < handlersSize; i++) { subscription = subscriptions[i]; subscription.subscribe(listener); // register this callback listener to this subscription @@ -282,28 +324,100 @@ class SubscriptionManager { // register for publication messageHandlerTypes = messageHandler.getHandledMessages(); - handlerType = messageHandlerTypes[0]; +// final int handlerSize = messageHandlerTypes.length; +// +// switch (handlerSize) { +// case 0: { +// handlerType = Void.class; +// +// // makes this subscription visible for publication +// final Subscription[] currentSubs = singleSubs.get(handlerType); +// final int currentLength = currentSubs.length; +// +// // add the new subscription to the array +// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); +// newSubs[currentLength] = subscription; +// singleSubs.put(handlerType, newSubs); +// break; +// } +// +// case 1: { + handlerType = messageHandlerTypes[0]; + + // makes this subscription visible for publication + final Subscription[] currentSubs = singleSubs.get(handlerType); + final int currentLength = currentSubs.length; + + // add the new subscription to the array + final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); + newSubs[currentLength] = subscription; + singleSubs.put(handlerType, newSubs); - // makes this subscription visible for publication - final Subscription[] currentSubs = localSubs.get(handlerType); - final int currentLength = currentSubs.length; + // update the varity/super types + // registerExtraSubs(handlerType, singleSubs, localSuperSubs, localVaritySubs); - // add the new subscription to the array - final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); - newSubs[currentLength] = subscription; - localSubs.put(handlerType, newSubs); - - // update the varity/super types -// registerExtraSubs(handlerType, localSubs, localSuperSubs, localVaritySubs); +// break; +// } +// +// case 2: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], +// messageHandlerTypes[1]); +// // makes this subscription visible for publication +// final Subscription[] currentSubs = multiSubs.get(multiClass); +// final int currentLength = currentSubs.length; +// +// // add the new subscription to the array +// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); +// newSubs[currentLength] = subscription; +// multiSubs.put(multiClass, newSubs); +// +// break; +// } +// +// case 3: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes[0], +// messageHandlerTypes[1], +// messageHandlerTypes[2]); +// // makes this subscription visible for publication +// final Subscription[] currentSubs = multiSubs.get(multiClass); +// final int currentLength = currentSubs.length; +// +// // add the new subscription to the array +// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); +// newSubs[currentLength] = subscription; +// multiSubs.put(multiClass, newSubs); +// +// break; +// } +// +// default: { +// final MultiClass multiClass = classTree.get(messageHandlerTypes); +// // makes this subscription visible for publication +// final Subscription[] currentSubs = multiSubs.get(multiClass); +// final int currentLength = currentSubs.length; +// +// // add the new subscription to the array +// final Subscription[] newSubs = Arrays.copyOf(currentSubs, currentLength + 1, Subscription[].class); +// newSubs[currentLength] = subscription; +// multiSubs.put(multiClass, newSubs); +// +// break; +// } +// } } - // save this snapshot back to the original (single writer principle) - subsSingleREF.lazySet(this, localSubs); + subsSingleREF.lazySet(this, singleSubs); +// subsMultiREF.lazySet(this, multiSubs); // subsSuperSingleREF.lazySet(this, localSuperSubs); // subsVaritySingleREF.lazySet(this, localVaritySubs); // subsSuperVaritySingleREF.lazySet(this, localSuperVaritySubs); + + + // only dump the super subscritions if it is a COMPLETELY NEW subscription. If it's not new, then the heirarchy isn't + // changing for super subscriptions + subsSuperSingleREF.lazySet(this, new IdentityMap(32)); } else { // subscriptions already exist and must only be updated @@ -425,85 +539,44 @@ class SubscriptionManager { return varArgUtils; } - // inside a write lock - // add this subscription to each of the handled types - // to activate this sub for publication - private - void registerMulti(final Subscription subscription, final Class listenerClass, - final Map, List> subsPerMessageSingle, - final HashMapTree, ArrayList> subsPerMessageMulti, final AtomicBoolean varArgPossibility) { - - final MessageHandler handler = subscription.getHandler(); - final Class[] messageHandlerTypes = handler.getHandledMessages(); - final int size = messageHandlerTypes.length; - - final Class type0 = messageHandlerTypes[0]; - - switch (size) { - case 0: { - // TODO: maybe this SHOULD be permitted? so if a publisher publishes VOID, it call's a method? - errorHandler.handleError("Error while trying to subscribe class with zero arguments", listenerClass); - return; - } - case 1: { -// // using ThreadLocal cache's is SIGNIFICANTLY faster for subscribing to new types -// final List cachedSubs = listCache.get(); -// List subs = subsPerMessageSingle.putIfAbsent(type0, cachedSubs); -// if (subs == null) { -// listCache.set(new CopyOnWriteArrayList()); -//// listCache.set(new ArrayList(8)); -// subs = cachedSubs; -// -// // is this handler able to accept var args? -// if (handler.getVarArgClass() != null) { -// varArgPossibility.lazySet(true); -// } -// } -// -// subs.add(subscription); - return; - } - case 2: { - ArrayList subs = subsPerMessageMulti.get(type0, messageHandlerTypes[1]); - if (subs == null) { - subs = new ArrayList(); - - subsPerMessageMulti.put(subs, type0, messageHandlerTypes[1]); - } - - subs.add(subscription); - return; - } - case 3: { - ArrayList subs = subsPerMessageMulti.get(type0, messageHandlerTypes[1], messageHandlerTypes[2]); - if (subs == null) { - subs = new ArrayList(); - - subsPerMessageMulti.put(subs, type0, messageHandlerTypes[1], messageHandlerTypes[2]); - } - - subs.add(subscription); - return; - } - default: { - ArrayList subs = subsPerMessageMulti.get(messageHandlerTypes); - if (subs == null) { - subs = new ArrayList(); - - subsPerMessageMulti.put(subs, messageHandlerTypes); - } - - subs.add(subscription); - } - } - } - // can return null public Subscription[] getSubs(final Class messageClass) { return (Subscription[]) subsSingleREF.get(this).get(messageClass); } + // can return null + public + Subscription[] getSubs(final Class messageClass1, final Class messageClass2) { + // never returns null + final MultiClass multiClass = classTree.get(messageClass1, + messageClass2); + return (Subscription[]) subsMultiREF.get(this).get(multiClass); + } + + // can return null + public + Subscription[] getSubs(final Class messageClass1, final Class messageClass2, final Class messageClass3) { + // never returns null + final MultiClass multiClass = classTree.get(messageClass1, + messageClass2, + messageClass3); + return (Subscription[]) subsMultiREF.get(this).get(multiClass); + } + + // can return null + public + Subscription[] getSubs(final Class[] messageClasses) { + // never returns null + final MultiClass multiClass = classTree.get(messageClasses); + + return (Subscription[]) subsMultiREF.get(this).get(multiClass); + } + + + + + // can return null public Subscription[] getSuperSubs(final Class messageClass) { @@ -514,8 +587,8 @@ class SubscriptionManager { final IdentityMap, Subscription[]> localSubs = subsSingleREF.get(this); - Subscription sub; Class superClass; + Subscription sub; Subscription[] superSubs; boolean hasSubs = false; @@ -543,184 +616,144 @@ class SubscriptionManager { return subsAsList.toArray(new Subscription[0]); } else { + // TODO: shortcut out if there are no handlers that accept subtypes return null; } -// IT IS CRITICAL TO REMEMBER: The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside then change) +// IT IS CRITICAL TO REMEMBER: The subscriptions that are remembered here DO NOT CHANGE (only the listeners inside them change). if we +// subscribe a super/child class -- THEN these subscriptions change! // return (Subscription[]) subsSuperSingleREF.get(this).get(messageClass); } - - public - ArrayList getExactAsArray(final Class messageClass1, final Class messageClass2) { - return subscriptionsPerMessageMulti.get(messageClass1, messageClass2); - } - - public - ArrayList getExactAsArray(final Class messageClass1, final Class messageClass2, final Class messageClass3) { - return subscriptionsPerMessageMulti.get(messageClass1, messageClass2, messageClass3); - } - - // can return null public - Subscription[] getSubs(final Class messageClass1, final Class messageClass2) { - final ArrayList collection = getExactAsArray(messageClass1, messageClass2); + Subscription[] getSuperSubs(final Class messageClass1, final Class messageClass2) { + // save the subscriptions + final Class[] superClasses1 = this.classUtils.getSuperClasses(messageClass1); // never returns null, cached response + final Class[] superClasses2 = this.classUtils.getSuperClasses(messageClass2); // never returns null, cached response - if (collection != null) { - return collection.toArray(new Subscription[0]); + final IdentityMap localSubs = subsMultiREF.get(this); + + Class superClass1; + Class superClass2; + Subscription sub; + Subscription[] superSubs; + boolean hasSubs = false; + + + final int length1 = superClasses1.length; + final int length2 = superClasses2.length; + + ArrayList subsAsList = new ArrayList(length1 + length2); + + for (int i = 0; i < length1; i++) { + superClass1 = superClasses1[i]; + + // only go over subtypes + if (superClass1 == messageClass1) { + continue; + } + + for (int j = 0; j < length2; j++) { + superClass2 = superClasses2[j]; + + // only go over subtypes + if (superClass2 == messageClass2) { + continue; + } + + // never returns null + final MultiClass multiClass = classTree.get(superClass1, + superClass2); + superSubs = localSubs.get(multiClass); + if (superSubs != null) { + for (int k = 0; k < superSubs.length; k++) { + sub = superSubs[k]; + + if (sub.getHandler().acceptsSubtypes()) { + subsAsList.add(sub); + hasSubs = true; + } + } + } + } } - return null; - } - - // can return null - public - Subscription[] getSubs(final Class messageClass1, final Class messageClass2, final Class messageClass3) { - - final ArrayList collection = getExactAsArray(messageClass1, messageClass2, messageClass3); - - if (collection != null) { - return collection.toArray(new Subscription[0]); + // subsAsList now contains ALL of the super-class subscriptions. + if (hasSubs) { + return subsAsList.toArray(new Subscription[0]); + } + else { + // TODO: shortcut out if there are no handlers that accept subtypes + return null; } - - return null; } - // can return null - public - Subscription[] getExactAndSuper_MUST_HAVE_SEPERATE_CALLS(final Class messageClass) { - Subscription[] collection = getSubs(messageClass); // can return null - - // now superClasses - Class[] types = ReflectionUtils.getSuperTypes(messageClass); - -// NOTE: have to recalculate SUPER classes for the specified class, because WE DO NOT know this class during subscription time! - // whenever our subscriptions change, this map is cleared. -// final Map, ArrayList> local = this.superClassSubscriptions; - -// ArrayList subs = local.get(clazz); -// -// if (subs == null) { -// // types was not empty, so collect subscriptions for each type and collate them -// -// // save the subscriptions -// final Class[] superClasses = this.superClass.getSuperClasses(clazz); // never returns null, cached response -// -// Class superClass; -// ArrayList superSubs; -// Subscription sub; -// -// final int length = superClasses.length; -// int superSubLength; -// subs = new ArrayList(length); -// -// for (int i = 0; i < length; i++) { -// superClass = superClasses[i]; -// superSubs = subscriber.getExactAsArray(superClass); -// -// if (superSubs != null) { -// superSubLength = superSubs.size(); -// for (int j = 0; j < superSubLength; j++) { -// sub = superSubs.get(j); -// -// if (sub.getHandler().acceptsSubtypes()) { -// subs.add(sub); -// } -// } -// } -// } -// -// subs.trimToSize(); -// local.put(clazz, subs); -// } -// -// return subs; - - - - -// -// final Subscription[] superSubscriptions = getSuperSubs(messageClass); // can return null -// -// if (collection != null) { -// if (superSubscriptions != null) { -// // but both into a single array -// final int length = collection.length; -// final int lengthSuper = superSubscriptions.length; -// -// final Subscription[] newSubs = new Subscription[length + lengthSuper]; -// System.arraycopy(collection, 0, newSubs, 0, length); -// System.arraycopy(superSubscriptions, 0, newSubs, length, lengthSuper); -// -// return newSubs; -// } -// -// return collection; -// } -// -// return superSubscriptions; - return null; - } // can return null public Subscription[] getExactAndSuper(final Class messageClass1, final Class messageClass2) { - ArrayList collection = getExactAsArray(messageClass1, messageClass2); // can return null - - // now publish superClasses - final ArrayList superSubs = this.subUtils.getSuperSubscriptions(messageClass1, messageClass2, - this); // NOT return null - - if (collection != null) { - collection = new ArrayList(collection); - - if (!superSubs.isEmpty()) { - collection.addAll(superSubs); - } - } - else if (!superSubs.isEmpty()) { - collection = superSubs; - } - - if (collection != null) { - return collection.toArray(new Subscription[0]); - } - else { +// ArrayList collection = getSubs(messageClass1, messageClass2); // can return null +// +// // now publish superClasses +// final ArrayList superSubs = this.subUtils.getSuperSubscriptions(messageClass1, messageClass2, +// this); // NOT return null +// +// if (collection != null) { +// collection = new ArrayList(collection); +// +// if (!superSubs.isEmpty()) { +// collection.addAll(superSubs); +// } +// } +// else if (!superSubs.isEmpty()) { +// collection = superSubs; +// } +// +// if (collection != null) { +// return collection.toArray(new Subscription[0]); +// } +// else { return null; - } +// } } // can return null public Subscription[] getExactAndSuper(final Class messageClass1, final Class messageClass2, final Class messageClass3) { - - ArrayList collection = getExactAsArray(messageClass1, messageClass2, messageClass3); // can return null - - // now publish superClasses - final ArrayList superSubs = this.subUtils.getSuperSubscriptions(messageClass1, messageClass2, messageClass3, - this); // NOT return null - - if (collection != null) { - collection = new ArrayList(collection); - - if (!superSubs.isEmpty()) { - collection.addAll(superSubs); - } - } - else if (!superSubs.isEmpty()) { - collection = superSubs; - } - - if (collection != null) { - return collection.toArray(new Subscription[0]); - } - else { +// +// ArrayList collection = getExactAsArray(messageClass1, messageClass2, messageClass3); // can return null +// +// // now publish superClasses +// final ArrayList superSubs = this.subUtils.getSuperSubscriptions(messageClass1, messageClass2, messageClass3, +// this); // NOT return null +// +// if (collection != null) { +// collection = new ArrayList(collection); +// +// if (!superSubs.isEmpty()) { +// collection.addAll(superSubs); +// } +// } +// else if (!superSubs.isEmpty()) { +// collection = superSubs; +// } +// +// if (collection != null) { +// return collection.toArray(new Subscription[0]); +// } +// else { return null; - } +// } + } + + + public + Subscription[] getSuperSubs(final Class messageClass1, final Class messageClass2, final Class messageClass3) { + return null; } }