package org.dynmap.utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.dynmap.Log; /** * scalable flags primitive - used for keeping track of potentially huge number of tiles * * Represents a flag for each tile, with 2D coordinates based on 0,0 origin. Flags are grouped * 64 x 64, represented by an array of 64 longs. Each set is stored in a hashmap, keyed by a long * computed by ((x/64)<<32)+(y/64). * */ public class TileFlags { private HashMap<Long, long[]> chunkmap = new HashMap<Long, long[]>(); private long last_key = Long.MAX_VALUE; private long[] last_row; private int count; // Number of 1 values public TileFlags() { } public List<String> save() { ArrayList<String> v = new ArrayList<String>(); StringBuilder sb = new StringBuilder(); for(Map.Entry<Long, long[]> ent : chunkmap.entrySet()) { long v1 = ent.getKey().longValue(); sb.append(String.format("%x/%x", ((v1>>32)&0xFFFFFFFFL), (v1 & 0xFFFFFFFFL) )); long[] val = ent.getValue(); for(long vv : val) { sb.append(String.format(":%x/%x", ((vv>>32) & 0xFFFFFFFFL), (vv & 0xFFFFFFFFL))); } v.add(sb.toString()); sb.setLength(0); } return v; } public void load(List<String> vals) { clear(); for(String v : vals) { String[] tok = v.split(":"); long[] row = new long[64]; try { String ss[] = tok[0].split("/"); long rowaddr = (Long.parseLong(ss[0], 16)<<32) | Long.parseLong(ss[1],16); for(int i = 0; (i < 64) && (i < (tok.length-1)); i++) { ss = tok[i+1].split("/"); row[i] = (Long.parseLong(ss[0], 16)<<32) | Long.parseLong(ss[1],16); count += Long.bitCount(row[i]); } chunkmap.put(rowaddr, row); } catch (NumberFormatException nfx) { Log.info("parse error - " + nfx); } } } public boolean getFlag(int x, int y) { long k = (((long)(x >> 6)) << 32) | (0xFFFFFFFFL & (long)(y >> 6)); long[] row; if(k == last_key) { row = last_row; } else { row = chunkmap.get(k); last_key = k; last_row = row; } if(row == null) return false; else return (row[y & 0x3F] & (1L << (x & 0x3F))) != 0; } public boolean setFlag(int x, int y, boolean f) { long k = (((long)(x >> 6)) << 32) | (0xFFFFFFFFL & (long)(y >> 6)); long[] row; if(k == last_key) { row = last_row; } else { row = chunkmap.get(k); last_key = k; last_row = row; } boolean prev = false; long mask = (1L << (x & 0x3F)); int idx = y & 0x3F; if(f) { if(row == null) { row = new long[64]; chunkmap.put(k, row); last_row = row; } else { prev = (row[idx] & mask) != 0L; } if(!prev) { row[idx] |= mask; count++; } } else { if(row != null) { prev = (row[idx] & mask) != 0; if(prev) { row[idx] &= ~(mask); count--; if(row[idx] == 0L) { // All zero in element? boolean nonzero = false; for(int i = 0; i < row.length; i++) { if(row[i] != 0L) { nonzero = true; break; } } if(!nonzero) { chunkmap.remove(k); last_row = null; last_key = Long.MAX_VALUE; } } } } } return prev; } /** * Logical OR - set all flags true that are true in given set * * @param flags - flags to be ORed with our flags */ public void union(TileFlags flags) { for(Map.Entry<Long, long[]> es : flags.chunkmap.entrySet()) { Long k = es.getKey(); long[] f = chunkmap.get(k); long[] nf = es.getValue(); if(f == null) { f = new long[64]; chunkmap.put(k, f); } for(int i = 0; i < f.length; i++) { count -= Long.bitCount(f[i]); f[i] = f[i] | nf[i]; count += Long.bitCount(f[i]); } } last_row = null; last_key = Long.MAX_VALUE; } public void clear() { chunkmap.clear(); last_row = null; last_key = Long.MAX_VALUE; count = 0; } // Number of ones public int countFlags() { return count; } public Iterator getIterator() { return new Iterator(); } public static class TileCoord { public int x; public int y; public TileCoord() { this.x = 0; this.y = 0; } public TileCoord(int x, int y) { this.x = x; this.y = y; } public boolean equals(Object o) { if(o instanceof TileCoord) { TileCoord tc = (TileCoord)o; return (tc.x == this.x) && (tc.y == this.y); } return false; } @Override public int hashCode() { return x ^ (y << 8); } } public class Iterator { private Long[] keySet; private int nextIndex; private int lastKeyIndex; Iterator() { this.keySet = new Long[1]; this.nextIndex = 0; this.lastKeyIndex = -1; } public TileFlags iterSource() { return TileFlags.this; } public boolean hasNext() { return !chunkmap.isEmpty(); } public boolean next(TileCoord coord) { if(count == 0) { return false; } while(true) { if(lastKeyIndex < 0) { Set<Long> ks = chunkmap.keySet(); nextIndex = 0; int kscnt = ks.size(); if (kscnt == 0) { return false; } keySet = ks.toArray(new Long[kscnt]); lastKeyIndex = 0; } for(; lastKeyIndex < keySet.length; lastKeyIndex++) { Long k = keySet[lastKeyIndex]; if(k == null) continue; long[] flgs = chunkmap.get(k); if(flgs != null) { /* Scan for next 1 after last index */ for( ; nextIndex < (64*64); nextIndex++) { int hidx = (nextIndex >> 6) & 0x3F; int vidx = (nextIndex & 0x3F); if (((flgs[vidx] >> hidx) & 0x1L) != 0L) { coord.x = (int)(((k >> 32) & 0xFFFFFFFFL) << 6) | hidx; coord.y = (int)((k & 0xFFFFFFFFL) << 6) | vidx; nextIndex++; return true; } } } nextIndex = 0; } lastKeyIndex = -1; } } } }