-
Notifications
You must be signed in to change notification settings - Fork 374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add p/uuid #2076
base: master
Are you sure you want to change the base?
feat: add p/uuid #2076
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #2076 +/- ##
==========================================
- Coverage 63.32% 63.10% -0.22%
==========================================
Files 548 548
Lines 78511 81134 +2623
==========================================
+ Hits 49719 51203 +1484
- Misses 25438 26511 +1073
- Partials 3354 3420 +66
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There is no other way to guarantee atomicity besides using time in environments where atomic is not supported. If we consider it further, it seems possible to generate a random value (using |
Thanks for your explanation. I will check the progress of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super cool, I can already see myself using it :)
I've left a few comments, otherwise it looks good 💯
|
||
func NewUUID() *UUID { | ||
u := &UUID{ | ||
StartTime: time.Date(2008, 11, 10, 23, 0, 0, 0, time.UTC).Unix() * 1000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is fixed, why not just use the constant unix time for this value?
Also, what is this value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed you are right it would be better to provide the data directly in Unix
which would avoid recalculating them at each function call.
The value used is the one used in snowflake
because it is based on the fact that go playground
starts with the temp of 2009-11-10 23:00:00 UTC
.
So I took the same as that of snowflake because I did not find the time.Now() of gno playground
and I believe that in gno
time.Now() is based on the return of the block time rather than the system time.
Do you have more information on this subject?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in gno time.Now() is based on the return of the block time rather than the system time.
Yes, it's the block execution context time, and same for all transactions within a block
Can you add a comment, and change this value to unix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in gno time.Now() is based on the return of the block time rather than the system time.
Yes, it's the block execution context time, and same for all transactions within a block
Can you add a comment, and change this value to unix?
Yep, I had already made this modification in this commit :)
353beb1
return buf | ||
} | ||
|
||
func bytesToHex(bs []byte) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about implementing this as:
return "0x
+ hex.EncodeToString(bs)`
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the idea of using hex.EncodeToString()
I hadn't thought of it.
However I think it's better to stick to the standard generation of UUIDs because 0x
indicates that the value is in hexadecimal but is not used in UUID display standards. For the structure you have to keep the -
to delimit the 5 groups and keep a quick visibility.
What do you think of this implementation?
hexStr := hex.EncodeToString(bytes)
return ufmt.Sprintf("%s-%s-%s-%s-%s",
hexStr[0:8],
hexStr[8:12],
hexStr[12:16],
hexStr[16:20],
hexStr[20:],
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good
TimestampLength uint8 = 32 | ||
MachineIDLength uint64 = 10 | ||
SequenceLength uint64 = 12 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more than "length" (which I understand immediately as bytes), it's bits (ie. TimestampBits
).
also, why typed? they could just be untyped constants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank for the clarification, I thinked length
it is a correct name for length of bits
.
I chose to keep these constants typed for several reasons:
-
Clarity: Explicit typing makes it clear that these constants are used in bit-related operations.
-
Error prevention: Ensure that operations follow expected constraints and avoid compilation errors, especially for bit shifts.
-
Performance: Typing optimizes performance and avoids implicit conversions.
-
Scalability: Code compatibility if the logic needs to evolve.
If untyped constants are more appropriate in this case, I'm open to hearing :)
machineIDMoveLength = SequenceLength | ||
timestampMoveLength = MachineIDLength + SequenceLength | ||
|
||
startTime uint32 = 1226354400 // 10 Nov 2008 23:00:00 UTC in seconds (Snowflake epoch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this for some kind of compatibility, or could it just be 1/1/24?
i see no reason to use epochs from other formats unless it's to ensure some kind of sequencing / compatiblity from existing IDs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your opinion and yes it's possible to just use 1/1/24 but can you check my comment with @zivkovicmilos on this subject ? 🙏🏼
#2076 (comment)
// Copy transformed ID into the second half of the array | ||
copy(bytes[8:], uint64ToBytes(id)) | ||
|
||
// Use bits from the Snowflake ID to generate the first half of the UUID | ||
bytes[0] = byte(id >> 60) | ||
bytes[1] = byte(id >> 52) | ||
bytes[2] = byte(id >> 44) | ||
bytes[3] = byte(id >> 36) | ||
bytes[4] = byte(id >> 28) | ||
bytes[5] = byte(id >> 20) | ||
bytes[6] = byte(id >> 12) | ||
bytes[7] = byte(id >> 4) | ||
|
||
// set the version and variant bits according to UUID specification. | ||
bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4 (random) | ||
bytes[8] = (bytes[8] & 0x3f) | 0x80 // variant 1 (RFC 4122) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, we're using the same id
both in uint64ToBytes
, and then copying it over in a slightly modified manner later in this function
I don't particularly like this. We're restricting the number of bits allotted to a timestamp to 32 bits, so we can generate 64-bit integers; when really UUIDs are 122-bits, so we could reasonably have timestamps of at the very least 40 bits (which IMO is more reasonable).
Additionally, this means that there are not 2^122 possible IDs out of this package, but only 2^64.
There needs to be a decision: is this a Snowflake ID generator or is this a UUID generator? Either is fine, but understanding the target is important; and if we use UUIDs we should try to use all 122 bits at our disposal to try to create really unique identifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what you're saying. Originally, I had opted for a 64-bit range, but I ran into a uniqueness problem with the entropy
value
, which was in uint32
.
So, to use the full 64-bit range, I changed TimestampBits
to 42 bits. Let me know what you think ;)
859cae9
This is a Snowflake ID generator because I want to keep the Timestamp present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I thought keeping the name UUID
was more explicit, but in light of your feedback and my desire to stick with a Snowflake ID
generator system, I will change the names to maintain consistency throughout 👍🏼
3a18918
Co-authored-by: Morgan <git@howl.moe>
current := u.getEntropy() | ||
elapsedTime := current - u.startTime | ||
u.lastTimestamp = current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you making math stuff between random and timestamp?
it looks like just waste CPU.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't "current" be the current time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems not "gno-ified" enough :)
can you also provide some examples, like a screenshot or something so that we can see various generated output examples just by reviewing the PR, please?
Output this my last commit 485ce8d: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finish corrective review
TimestampLength uint8 = 32 | ||
MachineIDLength uint64 = 10 | ||
SequenceLength uint64 = 12 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank for the clarification, I thinked length
it is a correct name for length of bits
.
I chose to keep these constants typed for several reasons:
-
Clarity: Explicit typing makes it clear that these constants are used in bit-related operations.
-
Error prevention: Ensure that operations follow expected constraints and avoid compilation errors, especially for bit shifts.
-
Performance: Typing optimizes performance and avoids implicit conversions.
-
Scalability: Code compatibility if the logic needs to evolve.
If untyped constants are more appropriate in this case, I'm open to hearing :)
// Copy transformed ID into the second half of the array | ||
copy(bytes[8:], uint64ToBytes(id)) | ||
|
||
// Use bits from the Snowflake ID to generate the first half of the UUID | ||
bytes[0] = byte(id >> 60) | ||
bytes[1] = byte(id >> 52) | ||
bytes[2] = byte(id >> 44) | ||
bytes[3] = byte(id >> 36) | ||
bytes[4] = byte(id >> 28) | ||
bytes[5] = byte(id >> 20) | ||
bytes[6] = byte(id >> 12) | ||
bytes[7] = byte(id >> 4) | ||
|
||
// set the version and variant bits according to UUID specification. | ||
bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4 (random) | ||
bytes[8] = (bytes[8] & 0x3f) | 0x80 // variant 1 (RFC 4122) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what you're saying. Originally, I had opted for a 64-bit range, but I ran into a uniqueness problem with the entropy
value
, which was in uint32
.
So, to use the full 64-bit range, I changed TimestampBits
to 42 bits. Let me know what you think ;)
859cae9
This is a Snowflake ID generator because I want to keep the Timestamp present.
current := u.getEntropy() | ||
elapsedTime := current - u.startTime | ||
u.lastTimestamp = current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
machineIDMoveLength = SequenceLength | ||
timestampMoveLength = MachineIDLength + SequenceLength | ||
|
||
startTime uint32 = 1226354400 // 10 Nov 2008 23:00:00 UTC in seconds (Snowflake epoch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your opinion and yes it's possible to just use 1/1/24 but can you check my comment with @zivkovicmilos on this subject ? 🙏🏼
#2076 (comment)
Hey @DIGIX666, can you please update this branch with |
// Copy transformed ID into the second half of the array | ||
copy(bytes[8:], uint64ToBytes(id)) | ||
|
||
// Use bits from the Snowflake ID to generate the first half of the UUID | ||
bytes[0] = byte(id >> 60) | ||
bytes[1] = byte(id >> 52) | ||
bytes[2] = byte(id >> 44) | ||
bytes[3] = byte(id >> 36) | ||
bytes[4] = byte(id >> 28) | ||
bytes[5] = byte(id >> 20) | ||
bytes[6] = byte(id >> 12) | ||
bytes[7] = byte(id >> 4) | ||
|
||
// set the version and variant bits according to UUID specification. | ||
bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4 (random) | ||
bytes[8] = (bytes[8] & 0x3f) | 0x80 // variant 1 (RFC 4122) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest adding a
UUID
package because I think it could be useful to have it available. I think it can further facilitate traceability and compatibility between different systems and apps as they would have a common standard for identifying entities.To improve
UUID
generation, I wanted to add a resolver likeSnowflake
, but from what I've searched,sync/atomic
is not supported on Gno. Is there another similar package that I might have missed ?