-
Notifications
You must be signed in to change notification settings - Fork 47
Expand file tree
/
Copy pathInventorySetupsPluginMessageHandler.java
More file actions
200 lines (187 loc) · 6.64 KB
/
Copy pathInventorySetupsPluginMessageHandler.java
File metadata and controls
200 lines (187 loc) · 6.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package inventorysetups;
import inventorysetups.ui.InventorySetupsPluginPanel;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.events.PluginMessage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
// PluginMessage API for other plugins to list and open/clear inventory setups. See #415.
//
// To use this from another plugin, post a PluginMessage on the EventBus with the "inventory-setups"
// namespace, e.g.:
//
// Map<String, Object> data = new HashMap<>();
// data.put("setup", "Vorkath");
// eventBus.post(new PluginMessage("inventory-setups", "view", data));
//
// Supported messages are documented on the constants below. To track the available setups, subscribe
// to PluginMessage and listen for "setups-changed" broadcasts, and post "get-setups" once on startup
// (posting is synchronous, so the collection you pass is filled before post() returns). Payload values
// are plain JDK types (String, int, Collection<String>) so they are visible across plugin classloaders.
@Slf4j
public class InventorySetupsPluginMessageHandler
{
public static final String API_NAMESPACE = "inventory-setups";
// Bumped when the contract below changes in a breaking way. Shipped in setups-changed as data["version"].
public static final int API_VERSION = 1;
// out: broadcast when the setups change. data["setups"] = List<String> of names, data["version"] = int.
public static final String API_MSG_SETUPS_CHANGED = "setups-changed";
// in: list setups on demand (for plugins that start after us). Put a mutable Collection<String> under
// "setups"; it is filled synchronously with the current setup names.
public static final String API_MSG_GET_SETUPS = "get-setups";
// in: open a setup, filtering the bank like the worn items menu. data["setup"] = name.
public static final String API_MSG_VIEW = "view";
// in: clear the current setup (like worn items "Close current setup"). data["setup"] = name to clear
// only when it is the active setup; omit to clear whatever is active.
public static final String API_MSG_CLEAR = "clear";
public static final String API_DATA_SETUPS = "setups";
public static final String API_DATA_SETUP = "setup";
public static final String API_DATA_VERSION = "version";
private final InventorySetupsPlugin plugin;
private final ClientThread clientThread;
private final EventBus eventBus;
private final InventorySetupsPluginPanel panel;
// Immutable snapshot of the setup names, republished on every change. Lets get-setups answer from any
// thread without touching the live list. Only written on the client thread (see broadcastSetupsChanged).
private volatile List<String> setupNamesSnapshot = List.of();
public InventorySetupsPluginMessageHandler(InventorySetupsPlugin plugin, ClientThread clientThread,
EventBus eventBus, InventorySetupsPluginPanel panel)
{
this.plugin = plugin;
this.clientThread = clientThread;
this.eventBus = eventBus;
this.panel = panel;
}
// Refresh the snapshot and notify listeners. Serialized onto the client thread because setups are
// mutated from both the client thread and the Swing EDT. Skips the post when the name list is unchanged,
// since updateConfig also fires on slot and note edits.
public void broadcastSetupsChanged()
{
clientThread.invoke(() ->
{
final List<String> names = buildSetupNames();
if (names.equals(setupNamesSnapshot))
{
return;
}
setupNamesSnapshot = names;
eventBus.post(new PluginMessage(API_NAMESPACE, API_MSG_SETUPS_CHANGED,
Map.of(API_DATA_SETUPS, names, API_DATA_VERSION, API_VERSION)));
});
}
public void handleMessage(final PluginMessage message)
{
if (!API_NAMESPACE.equals(message.getNamespace()))
{
return;
}
if (API_MSG_SETUPS_CHANGED.equals(message.getName()))
{
// Our own outgoing broadcast.
return;
}
switch (message.getName())
{
case API_MSG_GET_SETUPS:
{
handleGetSetups(message);
break;
}
case API_MSG_VIEW:
{
handleView(message);
break;
}
case API_MSG_CLEAR:
{
handleClear(message);
break;
}
default:
{
log.warn("Ignoring unsupported message '{}' in the {} namespace", message.getName(), API_NAMESPACE);
break;
}
}
}
private void handleGetSetups(final PluginMessage message)
{
final Object container = message.getData().getOrDefault(API_DATA_SETUPS, null);
if (container instanceof Collection)
{
// eventBus.post is synchronous, so the caller's collection is filled before its own
// post() call returns.
//noinspection unchecked
((Collection<String>) container).addAll(setupNamesSnapshot);
}
else
{
log.warn("Ignoring {} message without a Collection under '{}'", API_MSG_GET_SETUPS, API_DATA_SETUPS);
}
}
private void handleView(final PluginMessage message)
{
final Object nameObj = message.getData().getOrDefault(API_DATA_SETUP, null);
if (!(nameObj instanceof String))
{
log.warn("Ignoring {} message without a String under '{}'", API_MSG_VIEW, API_DATA_SETUP);
return;
}
final String targetName = (String) nameObj;
// Resolve and apply on the client thread, where the setups are otherwise accessed.
clientThread.invoke(() ->
{
final InventorySetup target = plugin.getInventorySetups().stream()
.filter(setup -> setup.getName().equals(targetName))
.findFirst()
.orElse(null);
if (target == null)
{
log.warn("Ignoring view request for unknown setup '{}'", targetName);
return;
}
panel.setCurrentInventorySetup(target, true);
});
}
private void handleClear(final PluginMessage message)
{
final Object nameObj = message.getData().getOrDefault(API_DATA_SETUP, null);
if (nameObj != null && !(nameObj instanceof String))
{
log.warn("Ignoring {} message with a non-String value under '{}'", API_MSG_CLEAR, API_DATA_SETUP);
return;
}
clientThread.invoke(() ->
{
final InventorySetup current = panel.getCurrentSelectedSetup();
if (current == null)
{
return;
}
if (nameObj == null)
{
// No name given: clear whatever setup is active.
panel.returnToOverviewPanel(false);
return;
}
// A name was given: only clear when it is the setup currently shown, so a caller never
// closes a setup the user switched to themselves.
if (current.getName().equals(nameObj))
{
panel.returnToOverviewPanel(false);
}
});
}
private List<String> buildSetupNames()
{
final List<String> names = new ArrayList<>(plugin.getInventorySetups().size());
for (final InventorySetup setup : plugin.getInventorySetups())
{
names.add(setup.getName());
}
return List.copyOf(names);
}
}