uniflake 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f80b6d4ffd25a72a9a88dcec412a922514d9c6e631ac4244c9dfb3d2e590018f
4
+ data.tar.gz: 5405b316f49e42ce17876f062302d44d447d165152138cc2286ce04d7e176070
5
+ SHA512:
6
+ metadata.gz: 39ae675c29e7de6674043d050d0ce7b133316b481a5be6b4026dd013312afd2b49cc75717760db94b14095ea680db4d960a0576270408b8588e7306861d80c59
7
+ data.tar.gz: 7c9270a843f01f0600fd0212dfbdfad0d4d1a1e26b7c60c6b3f143194a9d3a34248001ce5bdf9aae22a403fe061ad1089a48756d12c9699ce178e2537ebb7023
@@ -0,0 +1,18 @@
1
+ module Uniflake
2
+
3
+ class Number
4
+ BIT_LENGTH = 10
5
+ MAX_ID = (1 << BIT_LENGTH) - 1
6
+
7
+ attr_reader :id
8
+
9
+ def initialize(id = 0)
10
+ raise OverflowError, "invalid generator id (#{id} > #{MAX_ID})" if id > MAX_ID
11
+ @id = id
12
+ end
13
+
14
+ def to_i
15
+ @id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Uniflake::Generators
2
+
3
+ class Discord < Uniflake
4
+ # https://en.wikipedia.org/wiki/Snowflake_ID
5
+ DEFAULT_EPOCH_START_AT = Time.new(2015,01,01)
6
+ end
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ module Uniflake::Generators
2
+
3
+ class Snowflake < Uniflake
4
+ # https://en.wikipedia.org/wiki/Snowflake_ID
5
+ DEFAULT_EPOCH_START_AT = Time.at(0, 1288834974657, :millisecond)
6
+ end
7
+
8
+ end
@@ -0,0 +1,65 @@
1
+ module Uniflake::Generators
2
+
3
+ class Uniflake
4
+ STRUCTURE = {
5
+ timestamp: 41,
6
+ generator_id: 10,
7
+ sequence: 12
8
+ }
9
+
10
+ DEFAULT_EPOCH_START_AT = Time.new(2022, 01, 01)
11
+
12
+ MAX_TIMESTAMP = (1 << STRUCTURE[:timestamp]) - 1
13
+ MAX_GENERATOR_ID = (1 << STRUCTURE[:generator_id]) - 1
14
+ MAX_SEQUENCE = (1 << STRUCTURE[:sequence]) - 1
15
+
16
+ def initialize(sequence_resolver, epoch_start_at = self::class::DEFAULT_EPOCH_START_AT, generator_id = 0)
17
+ @generator_id = generator_id.to_i
18
+ raise Error, "invalid generator_id (#{@generator_id} > #{self::class::MAX_GENERATOR_ID})" if @generator_id > self::class::MAX_GENERATOR_ID
19
+
20
+ @epoch_start_at = epoch_start_at.strftime('%s%L').to_i
21
+ now = Time.now.strftime('%s%L').to_i
22
+ raise Error, "invalid epoch (#{@epoch_start_at} > #{now})" if @epoch_start_at > now
23
+
24
+ @sequence_resolver = sequence_resolver
25
+ end
26
+
27
+ def generate
28
+ now = Time.now.strftime('%s%L').to_i
29
+
30
+ sequence = @sequence_resolver.resolve(@generator_id, now, self::class::MAX_SEQUENCE)
31
+ raise Error, "invalid sequence (#{sequence} > #{self::class::MAX_SEQUENCE})" if sequence > self::class::MAX_SEQUENCE
32
+
33
+ timestamp = now - @epoch_start_at
34
+ raise Error, "invalid timestamp (#{timestamp} > #{self::class::MAX_TIMESTAMP})" if timestamp > self::class::MAX_TIMESTAMP
35
+
36
+ self.class.compose(timestamp, @generator_id, sequence)
37
+ end
38
+
39
+ def self.compose(timestamp, generator_id, sequence)
40
+ shift = 0
41
+ id = 0
42
+ self::STRUCTURE.keys.reverse.each do |field|
43
+ id += binding.local_variable_get(field) << shift
44
+ shift += self::STRUCTURE[field]
45
+ end
46
+ id
47
+ end
48
+
49
+ def parse(id)
50
+ fields = self.class.parse(id)
51
+ fields[:timestamp] += @epoch_start_at if fields.has_key? :timestamp
52
+ fields
53
+ end
54
+
55
+ def self.parse(id)
56
+ id = id.to_s(2).rjust(self::STRUCTURE.values.sum, "0")
57
+ fields = {}
58
+ self::STRUCTURE.each do |field, length|
59
+ fields[field] = id[0..length - 1].to_i(2)
60
+ id[0..length - 1] = id[length..-1]
61
+ end
62
+ fields
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Memory
4
+
5
+ def initialize
6
+ @sequences = {}
7
+ @last_time = {}
8
+ end
9
+
10
+ def resolve(int_generator_id, time, max_value = 0)
11
+ raise "invalid time" if time < @last_time[int_generator_id].to_i
12
+
13
+ @sequences[int_generator_id] = time == @last_time[int_generator_id].to_i ? @sequences[int_generator_id].to_i + 1 : 0
14
+ @last_time[int_generator_id] = time
15
+ @sequences[int_generator_id]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Random
4
+
5
+ def initialize
6
+ @prng = Random.new
7
+ end
8
+
9
+ def resolve(int_generator_id, time, max_value = 0)
10
+ raise "invalid time" if time < @last_time.to_i
11
+ @last_time = time
12
+ @prng.rand max_value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Redis
4
+
5
+ def initialize(client, key_prefix = '')
6
+ @client = client
7
+ @prefix = key_prefix
8
+ end
9
+
10
+ def resolve(int_generator_id, time, max_value = 0)
11
+ last_time = connection.get(@prefix + int_generator_id.to_s + "_last_time").to_i
12
+ raise "invalid time" if time < last_time
13
+
14
+ connection.set @prefix + int_generator_id.to_s + "_last_time", time
15
+
16
+ connection.eval lua, [@prefix + int_generator_id.to_s + time.to_s], ['0', '10']
17
+ end
18
+
19
+ def connection
20
+ if @client.class.method_defined? 'with'
21
+ @client.with { |connection| return connection }
22
+ end
23
+
24
+ @client
25
+ end
26
+
27
+ def lua
28
+ <<~LUA
29
+ if redis.call('set', KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then
30
+ return 0
31
+ else
32
+ return redis.call('incr', KEYS[1])
33
+ end
34
+ LUA
35
+ end
36
+ end
37
+ end
data/lib/uniflake.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Uniflake
2
+ require_relative 'uniflake/generators/uniflake.rb'
3
+ require_relative 'uniflake/generators/snowflake.rb'
4
+ require_relative 'uniflake/generators/discord.rb'
5
+
6
+ require_relative 'uniflake/sequence_resolvers/memory.rb'
7
+ require_relative 'uniflake/sequence_resolvers/random.rb'
8
+ require_relative 'uniflake/sequence_resolvers/redis.rb'
9
+
10
+ require_relative 'uniflake/generator_ids/number.rb'
11
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uniflake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - nebo7
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: ''
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/uniflake.rb
20
+ - lib/uniflake/generator_ids/number.rb
21
+ - lib/uniflake/generators/discord.rb
22
+ - lib/uniflake/generators/snowflake.rb
23
+ - lib/uniflake/generators/uniflake.rb
24
+ - lib/uniflake/sequence_resolvers/memory.rb
25
+ - lib/uniflake/sequence_resolvers/random.rb
26
+ - lib/uniflake/sequence_resolvers/redis.rb
27
+ homepage: https://github.com/nebo7/uniflake
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubygems_version: 3.2.22
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: ''
50
+ test_files: []