Skip to content

Commit 5d2480b

Browse files
authored
Merge pull request #50 from cjmang/thread-safety
Thread safety
2 parents bd0c329 + 3c17235 commit 5d2480b

File tree

13 files changed

+279
-124
lines changed

13 files changed

+279
-124
lines changed

src/main/java/dev/koifysh/archipelago/Client.java

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package dev.koifysh.archipelago;
22

3+
import dev.koifysh.archipelago.bounce.BouncedManager;
34
import com.google.gson.JsonElement;
45
import com.google.gson.JsonObject;
56
import dev.koifysh.archipelago.events.RetrievedEvent;
67
import dev.koifysh.archipelago.flags.ItemsHandling;
8+
import dev.koifysh.archipelago.bounce.DeathLinkHandler;
79
import dev.koifysh.archipelago.network.server.ConnectUpdatePacket;
810
import dev.koifysh.archipelago.network.server.RoomInfoPacket;
911
import dev.koifysh.archipelago.parts.DataPackage;
@@ -19,19 +21,22 @@
1921
import java.io.*;
2022
import java.net.URI;
2123
import java.net.URISyntaxException;
22-
import java.nio.CharBuffer;
2324
import java.nio.charset.StandardCharsets;
2425
import java.nio.file.Files;
2526
import java.nio.file.Path;
2627
import java.nio.file.Paths;
2728
import java.util.*;
29+
import java.util.concurrent.ConcurrentHashMap;
2830
import java.util.logging.Level;
2931
import java.util.logging.Logger;
3032

3133
public abstract class Client {
3234

3335
private final static Logger LOGGER = Logger.getLogger(Client.class.getName());
3436

37+
public static final Version protocolVersion = new Version(0, 6, 1);
38+
private final static Gson gson = new Gson();
39+
3540
private static final String OS = System.getProperty("os.name").toLowerCase();
3641

3742
private static final Path cachePath;
@@ -69,9 +74,7 @@ else if(xdg == null || xdg.isEmpty() )
6974

7075
protected Map<String,String> versions;
7176

72-
protected ArrayList<String> games;
73-
74-
private final static Gson gson = new Gson();
77+
protected List<String> games;
7578

7679
private int hintPoints;
7780

@@ -81,31 +84,33 @@ else if(xdg == null || xdg.isEmpty() )
8184

8285
private RoomInfoPacket roomInfo;
8386

84-
private final DataPackage dataPackage;
87+
private final DataPackage dataPackage = new DataPackage();
8588

8689
public static Client client;
8790

8891
private final LocationManager locationManager;
8992
private final ItemManager itemManager;
9093
private final EventManager eventManager;
91-
92-
public static final Version protocolVersion = new Version(0, 6, 1);
94+
private final BouncedManager bouncedManager;
95+
private final DeathLinkHandler deathLinkHandler;
9396

9497
private int team;
9598
private int slot;
96-
private HashMap<Integer, NetworkSlot> slotInfo;
99+
private Map<Integer, NetworkSlot> slotInfo;
97100
private String name = "Name not set";
98101
private String game = "Game not set";
99102
private String alias;
100-
private Set<String> tags = new HashSet<>();
103+
private final Set<String> tags = Collections.newSetFromMap(new ConcurrentHashMap<>());
101104
private int itemsHandlingFlags = 0b000;
102105

103106
public Client() {
104107
dataPackageLocation = datapackageCachePath;
105-
dataPackage = new DataPackage();
106108
eventManager = new EventManager();
107109
locationManager = new LocationManager(this);
108110
itemManager = new ItemManager(this);
111+
bouncedManager = new BouncedManager();
112+
deathLinkHandler = new DeathLinkHandler(this);
113+
bouncedManager.addHandler(deathLinkHandler);
109114
client = this;
110115
}
111116

@@ -124,7 +129,8 @@ public void setGame(String game) {
124129
*/
125130
public void setTags(Set<String> tags) {
126131
if (!this.tags.equals(tags)) {
127-
this.tags = tags;
132+
this.tags.clear();
133+
this.tags.addAll(tags);
128134
if (isConnected()) {
129135
ConnectUpdatePacket packet = new ConnectUpdatePacket();
130136
packet.tags = this.tags;
@@ -138,8 +144,7 @@ public void setTags(Set<String> tags) {
138144
* @param tag String tag to be added.
139145
*/
140146
public void addTag(String tag) {
141-
if (!this.tags.contains(tag)) {
142-
tags.add(tag);
147+
if(tags.add(tag)) {
143148
if (isConnected()) {
144149
ConnectUpdatePacket packet = new ConnectUpdatePacket();
145150
packet.tags = this.tags;
@@ -153,8 +158,7 @@ public void addTag(String tag) {
153158
* @param tag String tag to be removed.
154159
*/
155160
public void removeTag(String tag) {
156-
if (this.tags.contains(tag)) {
157-
tags.remove(tag);
161+
if(tags.remove(tag)) {
158162
if (isConnected()) {
159163
ConnectUpdatePacket packet = new ConnectUpdatePacket();
160164
packet.tags = this.tags;
@@ -352,7 +356,7 @@ void setTeam(int team) {
352356
this.team = team;
353357
}
354358

355-
void setSlotInfo(HashMap<Integer, NetworkSlot> slotInfo) {
359+
void setSlotInfo(Map<Integer, NetworkSlot> slotInfo) {
356360
this.slotInfo = slotInfo;
357361
}
358362

@@ -388,7 +392,7 @@ public RoomInfoPacket getRoomInfo() {
388392
return roomInfo;
389393
}
390394

391-
public HashMap<Integer, NetworkSlot> getSlotInfo() {return slotInfo;}
395+
public Map<Integer, NetworkSlot> getSlotInfo() {return slotInfo;}
392396

393397
/**
394398
* Works exactly like {@link #connect(URI, boolean)} with allowDowngrade set to true;
@@ -629,6 +633,14 @@ public EventManager getEventManager() {
629633
return eventManager;
630634
}
631635

636+
/**
637+
* @return the bounced packet handler
638+
*/
639+
public BouncedManager getBouncedManager()
640+
{
641+
return bouncedManager;
642+
}
643+
632644
/**
633645
* Uses DataStorage to save a value on the AP server.
634646
*
@@ -705,4 +717,26 @@ public int dataStorageGet(Collection<String> keys) {
705717
return getPacket.getRequestID();
706718
}
707719

720+
/**
721+
* Helper for sending a death link bounce packet. You can send these without enabling death link first, but it is frowned upon.
722+
* @param source A String that is the name of the player sending the death link (does not have to be slot name)
723+
* @param cause A String that is the cause of this death. may be empty.
724+
*/
725+
public void sendDeathlink(String source, String cause)
726+
{
727+
deathLinkHandler.sendDeathLink(source, cause);
728+
}
729+
730+
/**
731+
* Enable or disable receiving death links.
732+
* @param enabled set to TRUE to enable death links, FALSE to disable.
733+
*/
734+
public void setDeathLinkEnabled(boolean enabled) {
735+
if(enabled)
736+
addTag(DeathLinkHandler.DEATHLINK_TAG);
737+
else
738+
removeTag(DeathLinkHandler.DEATHLINK_TAG);
739+
}
740+
741+
708742
}

src/main/java/dev/koifysh/archipelago/ItemManager.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,45 @@
66
import dev.koifysh.archipelago.parts.NetworkItem;
77

88
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.concurrent.ConcurrentLinkedDeque;
11+
import java.util.concurrent.ConcurrentLinkedQueue;
12+
import java.util.concurrent.CopyOnWriteArrayList;
13+
import java.util.concurrent.atomic.AtomicInteger;
914

1015
public class ItemManager {
1116

1217

13-
Client client;
14-
WebSocket webSocket;
18+
private final Client client;
19+
private WebSocket webSocket;
1520

16-
ArrayList<NetworkItem> receivedItems = new ArrayList<>();
21+
private List<NetworkItem> receivedItems = new ArrayList<>();
1722

18-
int index;
23+
private final AtomicInteger index = new AtomicInteger();
1924

2025
public ItemManager(Client client) {
2126
this.client = client;
2227
}
2328

24-
public void receiveItems(ArrayList<NetworkItem> ids, int index) {
29+
public void receiveItems(List<NetworkItem> ids, int index) {
2530
if (index == 0) {
2631
receivedItems = new ArrayList<>();
2732
}
2833
if (receivedItems.size() == index) {
29-
receivedItems.addAll(ids);
34+
synchronized (this) {
35+
receivedItems.addAll(ids);
36+
}
3037
DataPackage dp = client.getDataPackage();
3138
int myTeam = client.getTeam();
32-
for (int i = this.index; i < receivedItems.size(); i++) {
39+
for (int i = this.index.get(); i < receivedItems.size(); i++) {
3340
NetworkItem item = receivedItems.get(i);
3441
item.itemName = dp.getItem(item.itemID, client.getGame());
3542
item.locationName = dp.getLocation(item.locationID, client.getSlotInfo().get(item.playerID).game);
3643
item.playerName = client.getRoomInfo().getPlayer(myTeam,item.playerID).alias;
3744
client.getEventManager().callEvent(new ReceiveItemEvent(item, i+1));
3845
}
3946

40-
this.index = receivedItems.size();
47+
this.index.set(receivedItems.size());
4148
}
4249
else {
4350
if(webSocket != null) {
@@ -47,27 +54,31 @@ public void receiveItems(ArrayList<NetworkItem> ids, int index) {
4754
}
4855
}
4956

50-
public void writeFromSave(ArrayList<NetworkItem> receivedItems, int index) {
51-
this.receivedItems = receivedItems;
52-
this.index = index;
57+
public void writeFromSave(List<NetworkItem> receivedItems, int index) {
58+
this.receivedItems = new ArrayList<>(receivedItems);
59+
this.index.set(index);
5360
}
5461

55-
public void setAPWebSocket(WebSocket webSocket) {
62+
void setAPWebSocket(WebSocket webSocket) {
5663
this.webSocket = webSocket;
5764
}
5865

5966
public int getIndex() {
60-
return index;
67+
return index.get();
6168
}
6269

63-
public ArrayList<NetworkItem> getReceivedItems() {
64-
return receivedItems;
70+
public List<NetworkItem> getReceivedItems() {
71+
synchronized (this) {
72+
return new ArrayList<>(receivedItems);
73+
}
6574
}
6675

67-
public ArrayList<Long> getReceivedItemIDs() {
68-
ArrayList<Long> ids = new ArrayList<>();
69-
for (NetworkItem receivedItem : receivedItems) {
70-
ids.add(receivedItem.itemID);
76+
public List<Long> getReceivedItemIDs() {
77+
List<Long> ids = new ArrayList<>();
78+
synchronized (this) {
79+
for (NetworkItem receivedItem : receivedItems) {
80+
ids.add(receivedItem.itemID);
81+
}
7182
}
7283
return ids;
7384
}

src/main/java/dev/koifysh/archipelago/LocationManager.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22

33
import dev.koifysh.archipelago.network.client.LocationChecks;
44

5-
import java.util.ArrayList;
6-
import java.util.Collection;
7-
import java.util.HashSet;
8-
import java.util.Set;
5+
import java.util.*;
6+
import java.util.concurrent.ConcurrentHashMap;
97

108
public class LocationManager {
119

12-
Client client;
13-
WebSocket webSocket;
10+
// TODO: why is this field unused?
11+
private final Client client;
12+
private WebSocket webSocket;
1413

15-
Set<Long> checkedLocations = new HashSet<>();
14+
private final Set<Long> checkedLocations = Collections.newSetFromMap(new ConcurrentHashMap<>());
1615

17-
Set<Long> missingLocations = new HashSet<>();
16+
private final Set<Long> missingLocations = Collections.newSetFromMap(new ConcurrentHashMap<>());
1817

1918
public LocationManager(Client client) {
2019
this.client = client;
@@ -60,7 +59,7 @@ public void resendAllCheckedLocations() {
6059
webSocket.sendPacket(packet);
6160
}
6261

63-
protected void setAPWebSocket(WebSocket webSocket) {
62+
void setAPWebSocket(WebSocket webSocket) {
6463
this.webSocket = webSocket;
6564
}
6665

@@ -77,7 +76,8 @@ public void addCheckedLocations(Set<Long> newLocations) {
7776
this.missingLocations.removeAll(newLocations);
7877
}
7978

80-
public void setMissingLocations(HashSet<Long> missingLocations) {
81-
this.missingLocations = missingLocations;
79+
public void setMissingLocations(Set<Long> missingLocations) {
80+
this.missingLocations.clear();
81+
this.missingLocations.addAll(missingLocations);
8282
}
8383
}

src/main/java/dev/koifysh/archipelago/WebSocket.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import dev.koifysh.archipelago.Print.APPrintPart;
1010
import dev.koifysh.archipelago.Print.APPrintType;
1111
import dev.koifysh.archipelago.flags.NetworkPlayer;
12-
import dev.koifysh.archipelago.helper.DeathLink;
1312
import dev.koifysh.archipelago.network.APPacket;
1413
import dev.koifysh.archipelago.network.ConnectionResult;
1514
import dev.koifysh.archipelago.network.client.*;
@@ -200,10 +199,10 @@ else if (part.type == APPrintType.locationID) {
200199
break;
201200
case Bounced:
202201
BouncedPacket bounced = gson.fromJson(packet, BouncedPacket.class);
203-
if (bounced.tags.contains("DeathLink"))
204-
DeathLink.receiveDeathLink(bounced);
205-
else
202+
if(!client.getBouncedManager().handle(bounced))
203+
{
206204
client.getEventManager().callEvent(new BouncedEvent(bounced.games, bounced.tags, bounced.slots, bounced.data));
205+
}
207206
break;
208207
case LocationInfo:
209208
LocationInfoPacket locations = gson.fromJson(packet, LocationInfoPacket.class);

0 commit comments

Comments
 (0)