Commit 4bf718669f177dfce2d1bd248920ef7c0ad2fe50
1 parent
61965517
adding MessageBus
Showing
5 changed files
with
507 additions
and
7 deletions
java/client/com/mumfrey/liteloader/client/api/LiteLoaderCoreAPIClient.java
... | ... | @@ -14,6 +14,7 @@ import com.mumfrey.liteloader.client.LiteLoaderCoreProviderClient; |
14 | 14 | import com.mumfrey.liteloader.core.LiteLoader; |
15 | 15 | import com.mumfrey.liteloader.core.api.LiteLoaderCoreAPI; |
16 | 16 | import com.mumfrey.liteloader.interfaces.ObjectFactory; |
17 | +import com.mumfrey.liteloader.messaging.MessageBus; | |
17 | 18 | |
18 | 19 | /** |
19 | 20 | * Client side of the core API |
... | ... | @@ -113,7 +114,8 @@ public class LiteLoaderCoreAPIClient extends LiteLoaderCoreAPI |
113 | 114 | ( |
114 | 115 | objectFactory.getEventBroker(), |
115 | 116 | objectFactory.getClientPluginChannels(), |
116 | - objectFactory.getServerPluginChannels() | |
117 | + objectFactory.getServerPluginChannels(), | |
118 | + MessageBus.getInstance() | |
117 | 119 | ); |
118 | 120 | } |
119 | 121 | |
... | ... | @@ -125,7 +127,7 @@ public class LiteLoaderCoreAPIClient extends LiteLoaderCoreAPI |
125 | 127 | { |
126 | 128 | return ImmutableList.<Observer>of |
127 | 129 | ( |
128 | - this.getObjectFactory().getModPanelManager() | |
130 | + this.getObjectFactory().getPanelManager() | |
129 | 131 | ); |
130 | 132 | } |
131 | 133 | ... | ... |
java/common/com/mumfrey/liteloader/core/LiteLoader.java
... | ... | @@ -46,6 +46,7 @@ import com.mumfrey.liteloader.interfaces.PanelManager; |
46 | 46 | import com.mumfrey.liteloader.interfaces.ObjectFactory; |
47 | 47 | import com.mumfrey.liteloader.launch.LoaderEnvironment; |
48 | 48 | import com.mumfrey.liteloader.launch.LoaderProperties; |
49 | +import com.mumfrey.liteloader.messaging.MessageBus; | |
49 | 50 | import com.mumfrey.liteloader.modconfig.ConfigManager; |
50 | 51 | import com.mumfrey.liteloader.modconfig.Exposable; |
51 | 52 | import com.mumfrey.liteloader.permissions.PermissionsManagerClient; |
... | ... | @@ -848,7 +849,7 @@ public final class LiteLoader |
848 | 849 | } |
849 | 850 | |
850 | 851 | // Get the mod panel manager |
851 | - this.panelManager = this.objectFactory.getModPanelManager(); | |
852 | + this.panelManager = this.objectFactory.getPanelManager(); | |
852 | 853 | if (this.panelManager != null) |
853 | 854 | { |
854 | 855 | this.panelManager.init(this.mods, this.configManager); |
... | ... | @@ -905,15 +906,14 @@ public final class LiteLoader |
905 | 906 | // Set the loader branding in ClientBrandRetriever using reflection |
906 | 907 | LiteLoaderBootstrap.setBranding("LiteLoader"); |
907 | 908 | |
908 | - for (CoreProvider coreProvider : this.coreProviders) | |
909 | - { | |
910 | - coreProvider.onStartupComplete(); | |
911 | - } | |
909 | + this.coreProviders.all().onStartupComplete(); | |
912 | 910 | |
913 | 911 | if (this.panelManager != null) |
914 | 912 | { |
915 | 913 | this.panelManager.onStartupComplete(); |
916 | 914 | } |
915 | + | |
916 | + MessageBus.getInstance().onStartupComplete(); | |
917 | 917 | } |
918 | 918 | |
919 | 919 | /** | ... | ... |
java/common/com/mumfrey/liteloader/messaging/Message.java
0 → 100644
1 | +package com.mumfrey.liteloader.messaging; | |
2 | + | |
3 | +import java.util.HashMap; | |
4 | +import java.util.Map; | |
5 | +import java.util.regex.Pattern; | |
6 | + | |
7 | +import com.google.common.collect.ImmutableMap; | |
8 | + | |
9 | +/** | |
10 | + * Class used to encapsulate a MessageBus message | |
11 | + * | |
12 | + * @author Adam Mummery-Smith | |
13 | + */ | |
14 | +public class Message | |
15 | +{ | |
16 | + /** | |
17 | + * Regex for matching valid channels | |
18 | + */ | |
19 | + private static final Pattern channelPattern = Pattern.compile("^[a-z0-9]([a-z0-9_\\-]*[a-z0-9])?:[a-z0-9]([a-z0-9_\\-]*[a-z0-9])?$", Pattern.CASE_INSENSITIVE); | |
20 | + | |
21 | + private final String channel, replyChannel; | |
22 | + private final Messenger sender; | |
23 | + private final Map<String, ?> payload; | |
24 | + | |
25 | + Message(String channel, Object value, Messenger sender) | |
26 | + { | |
27 | + this(channel, value, sender, null); | |
28 | + } | |
29 | + | |
30 | + Message(String channel, Object value, Messenger sender, String replyChannel) | |
31 | + { | |
32 | + Message.validateChannel(channel); | |
33 | + | |
34 | + this.channel = channel; | |
35 | + this.payload = ImmutableMap.<String, Object>of("value", value); | |
36 | + this.sender = sender; | |
37 | + this.replyChannel = replyChannel; | |
38 | + } | |
39 | + | |
40 | + Message(String channel, Map<String, ?> payload, Messenger sender) | |
41 | + { | |
42 | + this(channel, payload, sender, null); | |
43 | + } | |
44 | + | |
45 | + Message(String channel, Map<String, ?> payload, Messenger sender, String replyChannel) | |
46 | + { | |
47 | + Message.validateChannel(channel); | |
48 | + | |
49 | + this.channel = channel; | |
50 | + this.payload = payload != null ? ImmutableMap.copyOf(payload) : ImmutableMap.<String, String>of(); | |
51 | + this.sender = sender; | |
52 | + this.replyChannel = replyChannel; | |
53 | + } | |
54 | + | |
55 | + /** | |
56 | + * Get the channel (fully qualified) that this message was sent on | |
57 | + */ | |
58 | + public String getChannel() | |
59 | + { | |
60 | + return this.channel; | |
61 | + } | |
62 | + | |
63 | + /** | |
64 | + * Get the channel category for this message | |
65 | + */ | |
66 | + public String getCategory() | |
67 | + { | |
68 | + return this.channel.substring(0, this.channel.indexOf(':')); | |
69 | + } | |
70 | + | |
71 | + /** | |
72 | + * Get the specified reply channel (if any) for this message - may return null | |
73 | + */ | |
74 | + public String getReplyChannel() | |
75 | + { | |
76 | + return this.replyChannel; | |
77 | + } | |
78 | + | |
79 | + /** | |
80 | + * Get the message sender (if any) for this message - may return null | |
81 | + */ | |
82 | + public Messenger getSender() | |
83 | + { | |
84 | + return this.sender; | |
85 | + } | |
86 | + | |
87 | + /** | |
88 | + * Get the message payload | |
89 | + */ | |
90 | + public Map<String, ?> getPayload() | |
91 | + { | |
92 | + return this.payload; | |
93 | + } | |
94 | + | |
95 | + /** | |
96 | + * Check if this message is on the specified channel | |
97 | + * | |
98 | + * @param channel Full name of the channel to check against (case sensitive) | |
99 | + * @return | |
100 | + */ | |
101 | + public boolean isChannel(String channel) | |
102 | + { | |
103 | + return this.channel.equals(channel); | |
104 | + } | |
105 | + | |
106 | + /** | |
107 | + * Check if this message has the specified category | |
108 | + * | |
109 | + * @param category | |
110 | + * @return | |
111 | + */ | |
112 | + public boolean isCategory(String category) | |
113 | + { | |
114 | + return this.getCategory().equals(category); | |
115 | + } | |
116 | + | |
117 | + /** | |
118 | + * Get (and implicit cast) a value from this message's payload | |
119 | + * | |
120 | + * @param key | |
121 | + * @return | |
122 | + */ | |
123 | + @SuppressWarnings("unchecked") | |
124 | + public <T> T get(String key) | |
125 | + { | |
126 | + return (T)this.payload.get(key); | |
127 | + } | |
128 | + | |
129 | + @SuppressWarnings("unchecked") | |
130 | + public <T> T get(String key, T defaultValue) | |
131 | + { | |
132 | + Object value = this.payload.get(key); | |
133 | + if (value != null) | |
134 | + { | |
135 | + return (T)value; | |
136 | + } | |
137 | + return defaultValue; | |
138 | + } | |
139 | + | |
140 | + /** | |
141 | + * Gets the payload with the key "value", which is used with messages constructed using a string-only payload | |
142 | + */ | |
143 | + public <T> T getValue() | |
144 | + { | |
145 | + return this.get("value"); | |
146 | + } | |
147 | + | |
148 | + public static void validateChannel(String channel) throws IllegalArgumentException | |
149 | + { | |
150 | + if (channel == null) | |
151 | + { | |
152 | + throw new IllegalArgumentException("Channel name cannot be null"); | |
153 | + } | |
154 | + | |
155 | + if (!Message.isValidChannel(channel)) | |
156 | + { | |
157 | + throw new IllegalArgumentException("'" + channel + "' is not a valid channel name"); | |
158 | + } | |
159 | + } | |
160 | + | |
161 | + public static boolean isValidChannel(String channel) | |
162 | + { | |
163 | + return Message.channelPattern.matcher(channel).matches(); | |
164 | + } | |
165 | + | |
166 | + /** | |
167 | + * Build a KV map from interleaved keys and values, convenience function | |
168 | + * | |
169 | + * @param args | |
170 | + * @return | |
171 | + */ | |
172 | + public static Map<String, ?> buildMap(Object... args) | |
173 | + { | |
174 | + Map<String, Object> payload = new HashMap<String, Object>(); | |
175 | + for (int i = 0; i < args.length - 1; i += 2) | |
176 | + { | |
177 | + if (args[i] instanceof String) | |
178 | + { | |
179 | + payload.put((String)args[i], args[i + 1]); | |
180 | + } | |
181 | + } | |
182 | + | |
183 | + return payload; | |
184 | + } | |
185 | +} | ... | ... |
java/common/com/mumfrey/liteloader/messaging/MessageBus.java
0 → 100644
1 | +package com.mumfrey.liteloader.messaging; | |
2 | + | |
3 | +import java.util.Deque; | |
4 | +import java.util.HashMap; | |
5 | +import java.util.LinkedList; | |
6 | +import java.util.List; | |
7 | +import java.util.Map; | |
8 | + | |
9 | +import com.mumfrey.liteloader.api.InterfaceProvider; | |
10 | +import com.mumfrey.liteloader.api.Listener; | |
11 | +import com.mumfrey.liteloader.core.InterfaceRegistrationDelegate; | |
12 | +import com.mumfrey.liteloader.core.event.HandlerList; | |
13 | +import com.mumfrey.liteloader.interfaces.FastIterable; | |
14 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | |
15 | + | |
16 | +/** | |
17 | + * Intra-mod messaging bus, allows mods to send arbitrary notifications to each other without having to | |
18 | + * create an explicit dependency or resort to reflection | |
19 | + * | |
20 | + * @author Adam Mummery-Smith | |
21 | + */ | |
22 | +public class MessageBus implements InterfaceProvider | |
23 | +{ | |
24 | + /** | |
25 | + * Singleton | |
26 | + */ | |
27 | + private static MessageBus instance; | |
28 | + | |
29 | + /** | |
30 | + * Messengers subscribed to each channel | |
31 | + */ | |
32 | + private final Map<String, FastIterable<Messenger>> messengers = new HashMap<String, FastIterable<Messenger>>(); | |
33 | + | |
34 | + /** | |
35 | + * Pending messages dispatched pre-startup | |
36 | + */ | |
37 | + private final Deque<Message> messageQueue = new LinkedList<Message>(); | |
38 | + | |
39 | + private boolean enableMessaging = false; | |
40 | + | |
41 | + private MessageBus() | |
42 | + { | |
43 | + } | |
44 | + | |
45 | + /** | |
46 | + * Get the singleton instance | |
47 | + */ | |
48 | + public static MessageBus getInstance() | |
49 | + { | |
50 | + if (MessageBus.instance == null) | |
51 | + { | |
52 | + MessageBus.instance = new MessageBus(); | |
53 | + } | |
54 | + | |
55 | + return MessageBus.instance; | |
56 | + } | |
57 | + | |
58 | + /* (non-Javadoc) | |
59 | + * @see com.mumfrey.liteloader.api.InterfaceProvider#getListenerBaseType() | |
60 | + */ | |
61 | + @Override | |
62 | + public Class<? extends Listener> getListenerBaseType() | |
63 | + { | |
64 | + return Listener.class; | |
65 | + } | |
66 | + | |
67 | + /* (non-Javadoc) | |
68 | + * @see com.mumfrey.liteloader.api.InterfaceProvider#registerInterfaces(com.mumfrey.liteloader.core.InterfaceRegistrationDelegate) | |
69 | + */ | |
70 | + @Override | |
71 | + public void registerInterfaces(InterfaceRegistrationDelegate delegate) | |
72 | + { | |
73 | + delegate.registerInterface(Messenger.class); | |
74 | + } | |
75 | + | |
76 | + /* (non-Javadoc) | |
77 | + * @see com.mumfrey.liteloader.api.InterfaceProvider#initProvider() | |
78 | + */ | |
79 | + @Override | |
80 | + public void initProvider() | |
81 | + { | |
82 | + } | |
83 | + | |
84 | + /** | |
85 | + * | |
86 | + */ | |
87 | + public void onStartupComplete() | |
88 | + { | |
89 | + this.enableMessaging = true; | |
90 | + | |
91 | + while (this.messageQueue.size() > 0) | |
92 | + { | |
93 | + Message msg = this.messageQueue.pop(); | |
94 | + this.dispatchMessage(msg); | |
95 | + } | |
96 | + } | |
97 | + | |
98 | + public void registerMessenger(Messenger messenger) | |
99 | + { | |
100 | + List<String> messageChannels = messenger.getMessageChannels(); | |
101 | + if (messageChannels == null) | |
102 | + { | |
103 | + LiteLoaderLogger.warning("Listener %s returned a null channel list for getMessageChannels(), this could indicate a problem with the listener", messenger.getName()); | |
104 | + return; | |
105 | + } | |
106 | + | |
107 | + for (String channel : messageChannels) | |
108 | + { | |
109 | + if (channel != null && Message.isValidChannel(channel)) | |
110 | + { | |
111 | + LiteLoaderLogger.info("Listener %s is registering MessageBus channel %s", messenger.getName(), channel); | |
112 | + this.getMessengerList(channel).add(messenger); | |
113 | + } | |
114 | + else | |
115 | + { | |
116 | + LiteLoaderLogger.warning("Listener %s tried to register invalid MessageBus channel %s", messenger.getName(), channel); | |
117 | + } | |
118 | + } | |
119 | + } | |
120 | + | |
121 | + /** | |
122 | + * @param message | |
123 | + */ | |
124 | + private void sendMessage(Message message) | |
125 | + { | |
126 | + if (this.enableMessaging) | |
127 | + { | |
128 | + this.dispatchMessage(message); | |
129 | + return; | |
130 | + } | |
131 | + | |
132 | + this.messageQueue.push(message); | |
133 | + } | |
134 | + | |
135 | + /** | |
136 | + * @param message | |
137 | + */ | |
138 | + private void dispatchMessage(Message message) | |
139 | + { | |
140 | + try | |
141 | + { | |
142 | + FastIterable<Messenger> messengerList = this.messengers.get(message.getChannel()); | |
143 | + if (messengerList != null) | |
144 | + { | |
145 | + messengerList.all().receiveMessage(message); | |
146 | + } | |
147 | + } | |
148 | + catch (StackOverflowError err) | |
149 | + { | |
150 | + // A listener tried to reply on the same channel and ended up calling itself | |
151 | + throw new RuntimeException("Stack overflow encountered dispatching message on channel '" + message.getChannel() + "'. Did you reply to yourself?"); | |
152 | + } | |
153 | + } | |
154 | + | |
155 | + /** | |
156 | + * Get messengers for the specified channel | |
157 | + * | |
158 | + * @param channel | |
159 | + * @return | |
160 | + */ | |
161 | + private FastIterable<Messenger> getMessengerList(String channel) | |
162 | + { | |
163 | + FastIterable<Messenger> messengerList = this.messengers.get(channel); | |
164 | + if (messengerList == null) | |
165 | + { | |
166 | + messengerList = new HandlerList<Messenger>(Messenger.class); | |
167 | + this.messengers.put(channel, messengerList); | |
168 | + } | |
169 | + | |
170 | + return messengerList; | |
171 | + } | |
172 | + | |
173 | + /** | |
174 | + * Send an empty message on the specified channel, this is useful for messages which are basically just notifications | |
175 | + * | |
176 | + * @param channel | |
177 | + */ | |
178 | + public static void send(String channel) | |
179 | + { | |
180 | + Message message = new Message(channel, null, null); | |
181 | + MessageBus.getInstance().sendMessage(message); | |
182 | + } | |
183 | + | |
184 | + /** | |
185 | + * Send a message with a value on the specified channel | |
186 | + * | |
187 | + * @param channel | |
188 | + * @param value | |
189 | + */ | |
190 | + public static void send(String channel, String value) | |
191 | + { | |
192 | + Message message = new Message(channel, value, null); | |
193 | + MessageBus.getInstance().sendMessage(message); | |
194 | + } | |
195 | + | |
196 | + /** | |
197 | + * Send a message with a value on the specified channel from the specified sender | |
198 | + * | |
199 | + * @param channel | |
200 | + * @param value | |
201 | + * @param sender | |
202 | + */ | |
203 | + public static void send(String channel, String value, Messenger sender) | |
204 | + { | |
205 | + Message message = new Message(channel, value, sender); | |
206 | + MessageBus.getInstance().sendMessage(message); | |
207 | + } | |
208 | + | |
209 | + /** | |
210 | + * Send a message with a value on the specified channel from the specified sender | |
211 | + * | |
212 | + * @param channel | |
213 | + * @param value | |
214 | + * @param sender | |
215 | + * @param replyChannel | |
216 | + */ | |
217 | + public static void send(String channel, String value, Messenger sender, String replyChannel) | |
218 | + { | |
219 | + Message message = new Message(channel, value, sender, replyChannel); | |
220 | + MessageBus.getInstance().sendMessage(message); | |
221 | + } | |
222 | + | |
223 | + /** | |
224 | + * Send a message with a supplied payload on the specified channel | |
225 | + * | |
226 | + * @param channel | |
227 | + * @param payload | |
228 | + */ | |
229 | + public static void send(String channel, Map<String, ?> payload) | |
230 | + { | |
231 | + Message message = new Message(channel, payload, null); | |
232 | + MessageBus.getInstance().sendMessage(message); | |
233 | + } | |
234 | + | |
235 | + /** | |
236 | + * Send a message with a supplied payload on the specified channel from the specified sender | |
237 | + * | |
238 | + * @param channel | |
239 | + * @param payload | |
240 | + * @param sender | |
241 | + */ | |
242 | + public static void send(String channel, Map<String, ?> payload, Messenger sender) | |
243 | + { | |
244 | + Message message = new Message(channel, payload, sender); | |
245 | + MessageBus.getInstance().sendMessage(message); | |
246 | + } | |
247 | + | |
248 | + /** | |
249 | + * Send a message with a supplied payload on the specified channel from the specified sender | |
250 | + * | |
251 | + * @param channel | |
252 | + * @param payload | |
253 | + * @param sender | |
254 | + * @param replyChannel | |
255 | + */ | |
256 | + public static void send(String channel, Map<String, ?> payload, Messenger sender, String replyChannel) | |
257 | + { | |
258 | + Message message = new Message(channel, payload, sender, replyChannel); | |
259 | + MessageBus.getInstance().sendMessage(message); | |
260 | + } | |
261 | +} | ... | ... |
java/common/com/mumfrey/liteloader/messaging/Messenger.java
0 → 100644
1 | +package com.mumfrey.liteloader.messaging; | |
2 | + | |
3 | +import java.util.List; | |
4 | + | |
5 | +import com.mumfrey.liteloader.api.Listener; | |
6 | + | |
7 | +/** | |
8 | + * Interface for listeners that want to receive (or send) | |
9 | + * | |
10 | + * @author Adam Mummery-Smith | |
11 | + */ | |
12 | +public interface Messenger extends Listener | |
13 | +{ | |
14 | + /** | |
15 | + * Get listening channels for this Messenger. Channel names must follow the format: | |
16 | + * | |
17 | + * {category}:{channel} | |
18 | + * | |
19 | + * where both {category} and {channel} are alpha-numeric identifiers which can contain underscore or dash | |
20 | + * but must begin and end with only alpha-numeric characters: for example the following channel names are | |
21 | + * valid: | |
22 | + * | |
23 | + * * foo:bar | |
24 | + * * foo-bar:baz | |
25 | + * * foo-bar:baz_derp | |
26 | + * | |
27 | + * The following are INVALID: | |
28 | + * | |
29 | + * * foo | |
30 | + * * foo_:bar | |
31 | + * * _foo:bar | |
32 | + * | |
33 | + * In general, your listener should listen on channels all beginning with the same category, which may match | |
34 | + * your mod id. Channel names and categories are case-sensitive. | |
35 | + * | |
36 | + * @return List of channels to listen on | |
37 | + */ | |
38 | + public abstract List<String> getMessageChannels(); | |
39 | + | |
40 | + /** | |
41 | + * Called when a message matching a channel you have elected to listen on is dispatched by any agent. | |
42 | + * WARNING: this method is called if you dispatch a message on a channel you are listening to, thus you should | |
43 | + * AVOID replying on channels you are listening to UNLESS you specifically filter messages based on their sender: | |
44 | + * | |
45 | + * if (message.getSender() == this) return; | |
46 | + * | |
47 | + * Messages may have a null sender or payload but will never have a null channel. | |
48 | + * | |
49 | + * @param message | |
50 | + */ | |
51 | + public abstract void receiveMessage(Message message); | |
52 | +} | ... | ... |