Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ public boolean isExplored(long chunkIndex) {
return exploredChunks.contains(chunkIndex);
}

/**
* Clears all explored chunk data.
*/
public void clearExploredChunks() {
exploredChunks.clear();
}

/**
* Creates a clone of this component.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public void load(@Nonnull Player player, @Nonnull String worldName) {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(file)))) {
int version = in.readInt();
if (version != DATA_VERSION) {
LOGGER.warning("Unknown data version for player " + player.getDisplayName() + ": " + version);
LOGGER.warning("Incompatible exploration data version for player " + player.getDisplayName() + ": " + version + " (expected " + DATA_VERSION + ")");
return;
}

int count = in.readInt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private ExplorationTicker() {
* @return The ticker instance.
*/
@Nonnull
public static ExplorationTicker getInstance() {
public static synchronized ExplorationTicker getInstance() {
if (INSTANCE == null) {
INSTANCE = new ExplorationTicker();
}
Expand Down
131 changes: 82 additions & 49 deletions src/main/java/dev/ninesliced/exploration/ExploredChunksTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
Expand All @@ -21,10 +23,10 @@ public class ExploredChunksTracker {
private final ExplorationComponent persistentComponent;
private final ReadWriteLock lock = new ReentrantReadWriteLock();

private volatile long version = 0;
private volatile Set<Long> cachedSnapshot = null;
private volatile long cachedSnapshotVersion = -1;
private final AtomicLong version = new AtomicLong(0);

private record SnapshotCache(Set<Long> snapshot, long version) {}
private final AtomicReference<SnapshotCache> snapshotCache = new AtomicReference<>(null);

/**
* Creates a new tracker.
Expand All @@ -48,20 +50,25 @@ public ExploredChunksTracker(@Nullable ExplorationComponent component) {
*/
public boolean markChunkExplored(long chunkIndex) {
if (persistentComponent != null) {
boolean added = persistentComponent.addExploredChunk(chunkIndex);
if (added) {
version++;
cachedSnapshot = null;
lock.writeLock().lock();
try {
boolean added = persistentComponent.addExploredChunk(chunkIndex);
if (added) {
version.incrementAndGet();
snapshotCache.set(null);
}
return added;
} finally {
lock.writeLock().unlock();
}
return added;
}

lock.writeLock().lock();
try {
boolean added = memoryExploredChunks.add(chunkIndex);
if (added) {
version++;
cachedSnapshot = null;
version.incrementAndGet();
snapshotCache.set(null);
}
return added;
} finally {
Expand All @@ -79,17 +86,22 @@ public int markChunksExplored(@Nonnull Set<Long> chunkIndices) {
if (chunkIndices.isEmpty()) return 0;

if (persistentComponent != null) {
int added = 0;
for (Long chunk : chunkIndices) {
if (persistentComponent.addExploredChunk(chunk)) {
added++;
lock.writeLock().lock();
try {
int added = 0;
for (Long chunk : chunkIndices) {
if (persistentComponent.addExploredChunk(chunk)) {
added++;
}
}
if (added > 0) {
version.incrementAndGet();
snapshotCache.set(null);
}
return added;
} finally {
lock.writeLock().unlock();
}
if (added > 0) {
version++;
cachedSnapshot = null;
}
return added;
}

lock.writeLock().lock();
Expand All @@ -98,8 +110,8 @@ public int markChunksExplored(@Nonnull Set<Long> chunkIndices) {
memoryExploredChunks.addAll(chunkIndices);
int added = memoryExploredChunks.size() - sizeBefore;
if (added > 0) {
version++;
cachedSnapshot = null;
version.incrementAndGet();
snapshotCache.set(null);
}
return added;
} finally {
Expand All @@ -115,7 +127,12 @@ public int markChunksExplored(@Nonnull Set<Long> chunkIndices) {
*/
public boolean isChunkExplored(long chunkIndex) {
if (persistentComponent != null) {
return persistentComponent.isExplored(chunkIndex);
lock.readLock().lock();
try {
return persistentComponent.isExplored(chunkIndex);
} finally {
lock.readLock().unlock();
}
}

lock.readLock().lock();
Expand All @@ -134,27 +151,28 @@ public boolean isChunkExplored(long chunkIndex) {
* @return Unmodifiable set of all explored chunk indices.
*/
@Nonnull
@SuppressWarnings("null") // Collections.unmodifiableSet() is guaranteed non-null but lacks @Nonnull in this JDK
public Set<Long> getExploredChunks() {
long currentVersion = version;
Set<Long> snapshot = cachedSnapshot;
if (snapshot != null && cachedSnapshotVersion == currentVersion) {
return snapshot;
long currentVersion = version.get();
SnapshotCache cached = snapshotCache.get();
if (cached != null && cached.version() == currentVersion) {
return cached.snapshot();
}

if (persistentComponent != null) {
snapshot = Collections.unmodifiableSet(new HashSet<>(persistentComponent.getExploredChunks()));
} else {
lock.readLock().lock();
try {
snapshot = Collections.unmodifiableSet(new HashSet<>(memoryExploredChunks));
} finally {
lock.readLock().unlock();

Set<Long> newSnapshot;
lock.readLock().lock();
try {
if (persistentComponent != null) {
newSnapshot = Collections.unmodifiableSet(new HashSet<>(persistentComponent.getExploredChunks()));
} else {
newSnapshot = Collections.unmodifiableSet(new HashSet<>(memoryExploredChunks));
}
} finally {
lock.readLock().unlock();
}

cachedSnapshot = snapshot;
cachedSnapshotVersion = currentVersion;
return snapshot;

snapshotCache.set(new SnapshotCache(newSnapshot, currentVersion));
return newSnapshot;
}

/**
Expand All @@ -165,8 +183,13 @@ public Set<Long> getExploredChunks() {
*/
public void forEachExploredChunk(@Nonnull Consumer<Long> action) {
if (persistentComponent != null) {
for (Long chunk : persistentComponent.getExploredChunks()) {
action.accept(chunk);
lock.readLock().lock();
try {
for (Long chunk : persistentComponent.getExploredChunks()) {
action.accept(chunk);
}
} finally {
lock.readLock().unlock();
}
return;
}
Expand All @@ -188,7 +211,7 @@ public void forEachExploredChunk(@Nonnull Consumer<Long> action) {
* @return The current version.
*/
public long getVersion() {
return version;
return version.get();
}

/**
Expand All @@ -198,7 +221,12 @@ public long getVersion() {
*/
public int getExploredCount() {
if (persistentComponent != null) {
return persistentComponent.getExploredChunks().size();
lock.readLock().lock();
try {
return persistentComponent.getExploredChunks().size();
} finally {
lock.readLock().unlock();
}
}

lock.readLock().lock();
Expand All @@ -214,17 +242,22 @@ public int getExploredCount() {
*/
public void clear() {
if (persistentComponent != null) {
persistentComponent.getExploredChunks().clear();
version++;
cachedSnapshot = null;
lock.writeLock().lock();
try {
persistentComponent.clearExploredChunks();
version.incrementAndGet();
snapshotCache.set(null);
} finally {
lock.writeLock().unlock();
}
return;
}

lock.writeLock().lock();
try {
memoryExploredChunks.clear();
version++;
cachedSnapshot = null;
version.incrementAndGet();
snapshotCache.set(null);
} finally {
lock.writeLock().unlock();
}
Expand Down
10 changes: 4 additions & 6 deletions src/main/java/dev/ninesliced/managers/PlayerRadarManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -33,7 +32,7 @@ public class PlayerRadarManager {
private static final Logger LOGGER = Logger.getLogger(PlayerRadarManager.class.getName());
private static PlayerRadarManager instance;

private final Set<String> registeredWorlds = new HashSet<>();
private final Set<String> registeredWorlds = ConcurrentHashMap.newKeySet();
private final Map<String, List<RadarData>> worldRadarCache = new ConcurrentHashMap<>();
private final PlayerRadarProvider radarProvider;

Expand Down Expand Up @@ -146,25 +145,24 @@ public void registerForPlayer(@Nonnull Player player) {
public void registerForWorld(@Nonnull World world) {
String worldName = world.getName();

if (registeredWorlds.contains(worldName)) {
if (!registeredWorlds.add(worldName)) {
return;
}

try {
WorldMapManager mapManager = world.getWorldMapManager();
if (mapManager == null) {
registeredWorlds.remove(worldName);
LOGGER.warning("Cannot register radar provider: WorldMapManager is null for world " + worldName);
return;
}

if (!mapManager.getMarkerProviders().containsKey(PlayerRadarProvider.PROVIDER_ID)) {
mapManager.addMarkerProvider(PlayerRadarProvider.PROVIDER_ID, radarProvider);
registeredWorlds.add(worldName);
LOGGER.info("Registered PlayerRadarProvider for world: " + worldName);
} else {
registeredWorlds.add(worldName);
}
} catch (Exception e) {
registeredWorlds.remove(worldName);
LOGGER.warning("Failed to register radar provider for world " + worldName + ": " + e.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

/**
Expand All @@ -37,8 +39,8 @@ public class WaypointMigrationManager {
private static final String PERSONAL_ID_PREFIX = "user_personal_";
private static final String SHARED_ID_PREFIX = "user_shared_";

private static final Set<String> processedFiles = new HashSet<>();
private static boolean globalMigrationDone = false;
private static final Set<String> processedFiles = ConcurrentHashMap.newKeySet();
private static final AtomicBoolean globalMigrationDone = new AtomicBoolean(false);

private WaypointMigrationManager() {
}
Expand Down Expand Up @@ -71,11 +73,10 @@ public static void onPlayerJoin(@Nonnull Player player) {
return;
}

if (!globalMigrationDone) {
if (globalMigrationDone.compareAndSet(false, true)) {
for (Path dataDir : dataDirs) {
migrateGlobalWaypoints(world, dataDir);
}
globalMigrationDone = true;
}

for (Path dataDir : dataDirs) {
Expand All @@ -95,7 +96,7 @@ private static void migrateGlobalWaypoints(@Nonnull World world, @Nonnull Path d
}

String fileKey = globalFile.toString();
if (processedFiles.contains(fileKey)) {
if (!processedFiles.add(fileKey)) {
return;
}

Expand All @@ -108,7 +109,6 @@ private static void migrateGlobalWaypoints(@Nonnull World world, @Nonnull Path d
if (!root.has("Waypoints")) {
LOGGER.info("[Migration] No Waypoints array in global-pings.json");
deleteFile(globalFile);
processedFiles.add(fileKey);
return;
}

Expand Down Expand Up @@ -193,7 +193,6 @@ private static void migrateGlobalWaypoints(@Nonnull World world, @Nonnull Path d
LOGGER.info("[Migration] Migrated " + migratedCount + " global waypoints");

deleteFile(globalFile);
processedFiles.add(fileKey);

} catch (Exception e) {
LOGGER.warning("[Migration] Failed to migrate global waypoints: " + e.getMessage());
Expand Down Expand Up @@ -236,7 +235,7 @@ private static void migratePersonalWaypoints(@Nonnull Player player, @Nonnull Wo
*/
private static void migratePersonalFile(@Nonnull Player player, @Nonnull World world, @Nonnull Path file) {
String fileKey = file.toString();
if (processedFiles.contains(fileKey)) {
if (!processedFiles.add(fileKey)) {
return;
}

Expand All @@ -249,7 +248,6 @@ private static void migratePersonalFile(@Nonnull Player player, @Nonnull World w
if (!root.has("Waypoints")) {
LOGGER.info("[Migration] No Waypoints array in " + file.getFileName());
deleteFile(file);
processedFiles.add(fileKey);
return;
}

Expand Down Expand Up @@ -322,7 +320,6 @@ private static void migratePersonalFile(@Nonnull Player player, @Nonnull World w
LOGGER.info("[Migration] Migrated " + migratedCount + " personal waypoints from " + file.getFileName());

deleteFile(file);
processedFiles.add(fileKey);

} catch (Exception e) {
LOGGER.warning("[Migration] Failed to migrate personal waypoints from " + file.getFileName() + ": " + e.getMessage());
Expand Down