Skip to content

Instantly share code, notes, and snippets.

@arturaz
Created April 6, 2019 10:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arturaz/7144da1830caa5c01cdd4e48b845cd35 to your computer and use it in GitHub Desktop.
Save arturaz/7144da1830caa5c01cdd4e48b845cd35 to your computer and use it in GitHub Desktop.
JVM and .NET compatible GUID usable in Unity that can be converted back and forth without any allocations.
using com.tinylabproductions.TLPLib.Data;
using Quantum;
namespace Game.code.quantum_bind {
public static class Conversions {
public static unsafe QGuid toQGuid(this SerializableGUID guid) {
var srcBytes = stackalloc byte[16];
var dstBytes = stackalloc byte[16];
*((ulong*) srcBytes) = guid.long1;
*((ulong*) (srcBytes + 8)) = guid.long2;
// Some byte shuffling, because JVM and .NET decided to store those bytes differently for some
// reason.
dstBytes[0] = srcBytes[6];
dstBytes[1] = srcBytes[7];
dstBytes[2] = srcBytes[4];
dstBytes[3] = srcBytes[5];
dstBytes[4] = srcBytes[0];
dstBytes[5] = srcBytes[1];
dstBytes[6] = srcBytes[2];
dstBytes[7] = srcBytes[3];
dstBytes[8] = srcBytes[15];
dstBytes[9] = srcBytes[14];
dstBytes[10] = srcBytes[13];
dstBytes[11] = srcBytes[12];
dstBytes[12] = srcBytes[11];
dstBytes[13] = srcBytes[10];
dstBytes[14] = srcBytes[9];
dstBytes[15] = srcBytes[8];
return new QGuid(*((long*) dstBytes), *((long*) (dstBytes + 8)));
}
}
}
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using JetBrains.Annotations;
using Photon.Deterministic;
using Quantum.Core;
namespace Quantum {
/// <summary>
/// A runtime blittable, JVM compatible implementation of UUID.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack=CodeGenConstants.STRUCT_PACK), Serializable, PublicAPI]
public readonly struct QGuid : IEquatable<QGuid> {
public readonly long mostSigBits, leastSigBits;
public QGuid(long mostSigBits, long leastSigBits) {
this.mostSigBits = mostSigBits;
this.leastSigBits = leastSigBits;
}
public QGuid(byte[] data) {
#pragma warning disable 675
long msb = 0;
long lsb = 0;
if (data.Length != 16) throw new Exception("data must be 16 bytes in length");
for (var i=0; i<8; i++)
msb = (msb << 8) | (data[i] & 0xff);
for (var i=8; i<16; i++)
lsb = (lsb << 8) | (data[i] & 0xff);
mostSigBits = msb;
leastSigBits = lsb;
#pragma warning restore 675
}
#region Equality
public bool Equals(QGuid other) => mostSigBits == other.mostSigBits && leastSigBits == other.leastSigBits;
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) return false;
return obj is QGuid other && Equals(other);
}
public override int GetHashCode() {
unchecked {
return (mostSigBits.GetHashCode() * 397) ^ leastSigBits.GetHashCode();
}
}
public static bool operator ==(QGuid left, QGuid right) => left.Equals(right);
public static bool operator !=(QGuid left, QGuid right) => !left.Equals(right);
#endregion
public override string ToString() =>
digits(mostSigBits >> 32, 8) + "-" +
digits(mostSigBits >> 16, 4) + "-" +
digits(mostSigBits, 4) + "-" +
digits(leastSigBits >> 48, 4) + "-" +
digits(leastSigBits, 12);
public string asString => ToString();
/** Returns val represented by the specified number of hex digits. */
static string digits(long val, int digits) {
var hi = 1L << (digits * 4);
return (hi | (val & (hi - 1))).ToString("x").Substring(1);
}
static class RngProvider {
static readonly RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
static readonly byte[] bytes = new byte[16];
public static QGuid random() {
provider.GetBytes(bytes);
bytes[6] &= 0x0f; /* clear version */
bytes[6] |= 0x40; /* set to version 4 */
bytes[8] &= 0x3f; /* clear variant */
bytes[8] |= 0x80; /* set to IETF variant */
return new QGuid(bytes);
}
}
public static QGuid random() => RngProvider.random();
public static bool tryParseAllocating(string name, out QGuid guid) {
try {
guid = fromStringAllocatingThrowing(name);
return true;
}
catch {
guid = default;
return false;
}
}
public static QGuid fromStringAllocatingThrowing(string name) {
var components = name.Split('-');
if (components.Length != 5)
throw new Exception("Invalid UUID string: " + name);
var mostSigBits = Convert.ToInt64(components[0], 16);
mostSigBits <<= 16;
mostSigBits |= Convert.ToInt64(components[1], 16);
mostSigBits <<= 16;
mostSigBits |= Convert.ToInt64(components[2], 16);
var leastSigBits = Convert.ToInt64(components[3], 16);
leastSigBits <<= 48;
leastSigBits |= Convert.ToInt64(components[4], 16);
return new QGuid(mostSigBits, leastSigBits);
}
public static unsafe void Serialize(QGuid* guid, BitStream stream) {
if (stream.Writing) {
stream.WriteLong(guid->mostSigBits);
stream.WriteLong(guid->leastSigBits);
}
else {
*guid = new QGuid(stream.ReadLong(), stream.ReadLong());
}
}
}
}
using System;
using GenerationAttributes;
using JetBrains.Annotations;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
namespace com.tinylabproductions.TLPLib.Data {
[Serializable, InlineProperty, Record(GenerateConstructor = GeneratedConstructor.None, GenerateToString = false)]
public partial class SerializableGUID {
[SerializeField, FormerlySerializedAs("long1"), HideInInspector, PublicAccessor] ulong _long1;
[SerializeField, FormerlySerializedAs("long2"), HideInInspector, PublicAccessor] ulong _long2;
[CustomContextMenu("Generate new GUID", nameof(generate)), ShowInInspector, HideLabel]
string GUID {
get => guid.ToString();
set => guid = new Guid(value);
}
[PublicAPI] public void generate() => guid = Guid.NewGuid();
public SerializableGUID(Guid guid) {
this.guid = guid;
}
[PublicAPI] public Guid guid {
get => new Guid(
(uint) _long1,
(ushort) (_long1 >> 32),
(ushort) (_long1 >> (32 + 16)),
(byte) _long2,
(byte) (_long2 >> 8),
(byte) (_long2 >> (8 * 2)),
(byte) (_long2 >> (8 * 3)),
(byte) (_long2 >> (8 * 4)),
(byte) (_long2 >> (8 * 5)),
(byte) (_long2 >> (8 * 6)),
(byte) (_long2 >> (8 * 7))
);
private set {
var bytes = value.ToByteArray();
_long1 = BitConverter.ToUInt64(bytes, 0);
_long2 = BitConverter.ToUInt64(bytes, 8);
}
}
[PublicAPI] public bool isZero => _long1 == 0 && _long2 == 0;
public override string ToString() => guid.ToString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment