001package squidpony.squidmath; 002 003import squidpony.StringKit; 004 005import java.io.Serializable; 006 007/** 008 * A UUID-like identifier; not compatible with Java's standard UUID but will work on GWT. 009 * <br> 010 * Meant to be used as an identity type for things like SpatialMap, especially when no special game-specific logic is 011 * needed for identities. 012 * <br> 013 * Changed on April 27, 2018 so it isn't possible to generate two identical SquidIDs until 2 to the 128 minus 1 014 * SquidIDs have been generated, which will take a while. Before, there was a small possibility that even two sequential 015 * SquidIDs could be the same, and the new way gives a better guarantee of how many can be produced without duplicates. 016 * Changed again on December 7, 2018 so the class can be automatically serialized by libGDX's Json class and other ways 017 * of reflection on GWT. Now this uses 4 ints instead of 2 longs, since libGDX reflection couldn't be used to serialize 018 * the long fields this used before. The random number generator has the same guarantee of 2 to the 128 minus 1 IDs, but 019 * uses a different algorithm, and it can be restarted now using {@link #store()} and {@link #load(CharSequence)}. 020 * <br> 021 * Created by Tommy Ettinger on 4/30/2016. 022 */ 023public class SquidID implements Serializable, Comparable<SquidID> { 024 private static final long serialVersionUID = 8946534790126874460L; 025 private static XoshiroStarPhi32RNG rng = new XoshiroStarPhi32RNG(); 026 public final int a, b, c, d; 027 028 /** 029 * Constructs a new random SquidID. If you want different random IDs with every run, the defaults should be fine. 030 * If you want stable IDs to be generated, use SquidID.stabilize(), but be careful about collisions! 031 */ 032 public SquidID() { 033 a = rng.getStateA(); 034 b = rng.getStateB(); 035 c = rng.getStateC(); 036 d = rng.getStateD(); 037 rng.nextInt(); 038 039 } 040 041 /** 042 * Constructs a fixed SquidID with the given four 32-bit ints, which will be used exactly. 043 * @param a the least-significant bits of the ID 044 * @param b the second-to-least-significant bits of the ID 045 * @param c the second-to-most-significant bits of the ID 046 * @param d the most-significant bits of the ID 047 */ 048 public SquidID(int a, int b, int c, int d) { 049 this.a = a; 050 this.b = b; 051 this.c = c; 052 this.d = d; 053 } 054 055 /** 056 * Constructs a fixed SquidID with the given low and high 64-bit longs. 057 * @param low the least-significant bits of the ID 058 * @param high the most-significant bits of the ID 059 */ 060 public SquidID(long low, long high) { 061 a = (int)(low & 0xFFFFFFFFL); 062 b = (int)(low >>> 32); 063 c = (int)(high & 0xFFFFFFFFL); 064 d = (int)(high >>> 32); 065 } 066 067 /** 068 * Gets a new random SquidID, the same as calling the no-argument constructor. 069 * The name is for compatibility with Java's standard UUID class. 070 * @return a newly-constructed random SquidID. 071 */ 072 public static SquidID randomUUID() 073 { 074 return new SquidID(); 075 } 076 077 /** 078 * Makes the IDs generated after calling this repeatable, with the same IDs generated in order after this is called. 079 * This class uses a random number generator with a random seed by default to produce IDs, and properties of the 080 * {@link XoshiroStarPhi32RNG} this uses make it incredibly unlikely that IDs will repeat even if the game was run 081 * for years without stopping. For the purposes of tests, you may want stable SquidID values to be generated, the 082 * same for every startup of the program, generating the same IDs in order. This will change the seed used 083 * internally to a constant (large) seed the first time it is called, and it should only be called at or near the 084 * start of your program, no more than once. If an ID is requested immediately after calling this method, and then 085 * this method is called again, the next ID to be generated will be identical to the previous one generated (a 086 * collision). There may be reasons you want this during testing, so there isn't any check for multiple calls to 087 * this method. If IDs can persist between runs of the game (i.e. saved in a file), using this is generally a bad 088 * idea, and you should instead use either random IDs or save the state with. 089 * <br> 090 * You can "undo" the effects of this method with randomize(), changing the seed to a new random value. 091 * <br> 092 * Because IDs aren't likely to have gameplay significance, this uses one seed, and is equivalent to calling 093 * {@code SquidID.load("C13FA9A902A6328F91E10DA5C79E7B1D")}, which are values based on the Plastic Constant (2 to 094 * the 64 divided by the plastic constant, upper 32 bits and then lower 32 bits, then the upper and lower bits of 095 * that number divided again by the plastic constant). Irrational numbers like the plastic constant generally have a 096 * good distribution of bits, which should help delay the point when the generator hits "zeroland" and produces 097 * multiple small numbers for a short while. 098 */ 099 public static void stabilize() 100 { 101 rng.setState(0xC13FA9A9, 0x02A6328F, 0x91E10DA5, 0xC79E7B1D); 102 } 103 104 public static StringBuilder store() 105 { 106 final StringBuilder sb = new StringBuilder(32); 107 StringKit.appendHex(sb, rng.getStateA()); 108 StringKit.appendHex(sb, rng.getStateB()); 109 StringKit.appendHex(sb, rng.getStateC()); 110 return StringKit.appendHex(sb, rng.getStateD()); 111 } 112 /** 113 * Makes the IDs generated after calling this repeatable, with the same IDs generated in order after this is called. 114 * This class uses a random number generator with a random seed by default to produce IDs, and properties of the 115 * {@link XoshiroStarPhi32RNG} this uses make it incredibly unlikely that IDs will repeat even if the game was run 116 * for years without stopping. For the purposes of tests, you may want stable SquidID values to be generated, the 117 * same for every startup of the program, generating the same IDs in order; you may also want this when loading a 118 * saved game. This will change the seed used internally to match a value that should have been produced by 119 * {@link #store()} but can be any 32 hex digits, and it should only be called at or near the start of your program, 120 * no more than once per load of a save. If an ID is requested immediately after calling this method, and then this 121 * method is called again with the same data parameter, the next ID to be generated will be identical to the 122 * previous one generated (a collision). There may be reasons you want this during testing, so there isn't any check 123 * for multiple calls to this method. If IDs can persist between runs of the game (i.e. saved in a file), you can be 124 * fine with random IDs in almost all cases, or you can have more certainty by saving the last state of the 125 * generator using {@link #store()} when saving and loading that state with this method later. 126 * <br> 127 * You can "undo" the effects of this method with randomize(), changing the seed to a new random value. 128 */ 129 public static void load(CharSequence data) 130 { 131 rng.setState(StringKit.intFromHex(data, 0, 8), StringKit.intFromHex(data, 8, 16), 132 StringKit.intFromHex(data, 16, 24), StringKit.intFromHex(data, 24, 32)); 133 } 134 135 /** 136 * Makes the IDs generated after calling this non-repeatable, with a random 128-bit seed. 137 * This class uses a random number generator with a random seed by default to produce IDs, and properties of the 138 * {@link XoshiroStarPhi32RNG} this uses make it incredibly unlikely that IDs will repeat even if the game was run 139 * for years without stopping. However, if you call stabilize(), generate some IDs, call stabilize() again, and 140 * generate some more IDs, the first, second, third, etc. IDs generated after each call will be identical -- hardly 141 * the unique ID you usually want. You can "undo" the effects of stabilize by calling this method, making the seed a 142 * new random value. This does not affect the constructor that takes two longs to produce an exact ID, nor will it 143 * change any existing IDs. 144 */ 145 public static void randomize() 146 { 147 rng.setState((int)((Math.random() * 2.0 - 1.0) * 0x80000000), (int)((Math.random() * 2.0 - 1.0) * 0x80000000), 148 (int)((Math.random() * 2.0 - 1.0) * 0x80000000), (int)((Math.random() * 2.0 - 1.0) * 0x80000000)); 149 } 150 151 /** 152 * Gets the least-significant bits, also accessible by the field low. 153 * The name is for compatibility with Java's standard UUID class. 154 * @return the least-significant bits as a long 155 */ 156 public long getLeastSignificantBits() 157 { 158 return (long)b << 32 | (a & 0xFFFFFFFFL); 159 } 160 161 /** 162 * Gets the most-significant bits, also accessible by the field high. 163 * The name is for compatibility with Java's standard UUID class. 164 * @return the most-significant bits as a long 165 */ 166 public long getMostSignificantBits() 167 { 168 return (long)d << 32 | (c & 0xFFFFFFFFL); 169 } 170 171 /** 172 * Gets the 32 least-significant bits as an int. 173 * @return an int with the 32 least-significant bits of this ID 174 */ 175 public int getA() { 176 return a; 177 } 178 179 /** 180 * Gets the 32 second-to-least-significant bits as an int. 181 * @return an int with the 32 second-to-least-significant bits of this ID 182 */ 183 public int getB() { 184 return b; 185 } 186 /** 187 * Gets the 32 second-to-most-significant bits as an int. 188 * @return an int with the 32 second-to-most-significant bits of this ID 189 */ 190 public int getC() { 191 return c; 192 } 193 /** 194 * Gets the 32 most-significant bits as an int. 195 * @return an int with the 32 most-significant bits of this ID 196 */ 197 public int getD() { 198 return d; 199 } 200 201 @Override 202 public boolean equals(Object o) { 203 if (this == o) return true; 204 if (o == null || getClass() != o.getClass()) return false; 205 206 SquidID squidID = (SquidID) o; 207 208 return a == squidID.a && b == squidID.b && c == squidID.c && d == squidID.d; 209 210 } 211 212 @Override 213 public int hashCode() { 214 return 31 * 31 * 31 * a + 215 31 * 31 * b + 216 31 * c + 217 d; 218 } 219 220 @Override 221 public String toString() 222 { 223 return StringKit.hex(d) + StringKit.hex(c) + '-' + StringKit.hex(b) + StringKit.hex(a); 224 } 225 226 @Override 227 public int compareTo(SquidID o) { 228 if(o == null) 229 return 1; 230 int diff = d - o.d; 231 if(diff == 0) 232 diff = c - o.c; 233 if(diff == 0) 234 diff = b - o.b; 235 if(diff == 0) 236 diff = a - o.a; 237 return diff > 0 ? 1 : diff < 0 ? -1 : 0; 238 } 239}