package hunternif.mc.atlas.marker; import hunternif.mc.atlas.util.ListMapValueIterator; import hunternif.mc.atlas.util.ShortVec2; import java.util.AbstractCollection; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; public class DimensionMarkersData { private final MarkersData parent; private final int dimension; private int size = 0; private final Map<ShortVec2 /*chunk coords*/, List<Marker>> chunkMap = new ConcurrentHashMap<>(2, 0.75f, 2); private final Values values = new Values(); /** Maps threads to the temporary key for thread-safe access to chunkMap. */ private final Map<Thread, ShortVec2> thread2KeyMap = new ConcurrentHashMap<>(2, 0.75f, 2); /** Temporary key for thread-safe access to chunkMap. */ private ShortVec2 getKey() { return thread2KeyMap.computeIfAbsent(Thread.currentThread(), k -> new ShortVec2(0, 0)); } public DimensionMarkersData(MarkersData parent, int dimension) { this.parent = parent; this.dimension = dimension; } public int getDimension() { return dimension; } /** The "chunk" here is {@link MarkersData#CHUNK_STEP} times larger than the * Minecraft 16x16 chunk! */ public List<Marker> getMarkersAtChunk(int x, int z) { return chunkMap.get(getKey().set(x, z)); } /** Insert marker into a list at chunk coordinates, maintaining the ordering * of the list by Z coordinate. */ public void insertMarker(Marker marker) { ShortVec2 key = getKey().set( marker.getChunkX() / MarkersData.CHUNK_STEP, marker.getChunkZ() / MarkersData.CHUNK_STEP); List<Marker> list = chunkMap.get(key); if (list == null) { list = new CopyOnWriteArrayList<>(); chunkMap.put(key.clone(), list); } boolean inserted = false; for (int i = 0; i < list.size(); i++) { if (list.get(i).getZ() > marker.getZ()) { list.add(i, marker); inserted = true; break; } } if (!inserted) { list.add(marker); } size++; parent.markDirty(); } public boolean removeMarker(Marker marker) { size--; return getMarkersAtChunk( marker.getChunkX() / MarkersData.CHUNK_STEP, marker.getChunkZ() / MarkersData.CHUNK_STEP).remove(marker); } /** The returned view is immutable, i.e. remove() won't work. */ public Collection<Marker> getAllMarkers() { return values; } private class Values extends AbstractCollection<Marker> { @Override public Iterator<Marker> iterator() { return new ListMapValueIterator<>(chunkMap).setImmutable(true); } @Override public int size() { return size; } } }