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}