From f431abb7006fb09518c53c4be907521c430bb49c Mon Sep 17 00:00:00 2001 From: Taoning Ge Date: Thu, 12 Sep 2024 16:05:45 +0800 Subject: [PATCH] feat: impl for snowflake4cj --- .editorconfig | 12 ++++++ .gitignore | 8 ++++ README.md | 36 ++++++++++++++++ cjpm.lock | 3 ++ cjpm.toml | 14 ++++++ src/snowflake.cj | 1 + src/snowflake/snowflake.cj | 75 +++++++++++++++++++++++++++++++++ src/snowflake/snowflake_test.cj | 31 ++++++++++++++ 8 files changed, 180 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cjpm.lock create mode 100644 cjpm.toml create mode 100644 src/snowflake.cj create mode 100644 src/snowflake/snowflake.cj create mode 100644 src/snowflake/snowflake_test.cj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c1e2c64 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f956a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.vscode/ +.cache/ +*.macrocall +tmpFile* +*.gcno +*.gcda + +target/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddd8daa --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +
+

snowflake4cj

+

Snowflake algorithm for Cangjie

+
+

+ + +

+ +## 介绍 / Introduction + +snowflake4cj 是一个基于 Cangjie 的雪花算法实现。 + +snowflake4cj is a snowflake algorithm implementation based on Cangjie. + +## 安装 / Installation + +```toml +# In the `dependencies` section of `cjpm.toml` +snowflake4cj = { git = "https://github.com/gtn1024/snowflake4cj.git", tag = "v1.0.0" } +``` + +## 使用 / Usage + +```cj +import snowflake4cj.snowflake.Snowflake + +main(): Int64 { + let snowflake = Snowflake(0, 0) + for (i in 0..1000) { + let id = snowflake.nextId() + println(id) + } + return 0 +} +``` diff --git a/cjpm.lock b/cjpm.lock new file mode 100644 index 0000000..c42311c --- /dev/null +++ b/cjpm.lock @@ -0,0 +1,3 @@ +version = 0 + +[requires] diff --git a/cjpm.toml b/cjpm.toml new file mode 100644 index 0000000..c23ac34 --- /dev/null +++ b/cjpm.toml @@ -0,0 +1,14 @@ +[dependencies] + +[package] + cjc-version = "0.55.3" + compile-option = "" + description = "Snowflake algorithm for Cangjie" + link-option = "" + name = "snowflake4cj" + output-type = "dynamic" + override-compile-option = "" + src-dir = "" + target-dir = "" + version = "1.0.0" + package-configuration = {} diff --git a/src/snowflake.cj b/src/snowflake.cj new file mode 100644 index 0000000..a5851d6 --- /dev/null +++ b/src/snowflake.cj @@ -0,0 +1 @@ +package snowflake4cj diff --git a/src/snowflake/snowflake.cj b/src/snowflake/snowflake.cj new file mode 100644 index 0000000..daddca7 --- /dev/null +++ b/src/snowflake/snowflake.cj @@ -0,0 +1,75 @@ +package snowflake4cj.snowflake + +import std.time.* +import std.sync.* + +public class Snowflake { + internal let twepoch: Int64 = 1725148800000 + internal let workerIdBits: Int64 = 5 + internal let datacenterIdBits: Int64 = 5 + internal let maxWorkerId: Int64 = -1 ^ (-1 << workerIdBits) + internal let maxDatacenterId: Int64 = -1 ^ (-1 << datacenterIdBits) + internal let sequenceBits: Int64 = 12 + internal let workerIdShift: Int64 = sequenceBits + internal let datacenterIdShift: Int64 = sequenceBits + workerIdBits + internal let timestampLeftShift: Int64 = sequenceBits + workerIdBits + datacenterIdBits + internal let sequenceMask: Int64 = -1 ^ (-1 << sequenceBits) + + internal let workerId: Int64 + internal let datacenterId: Int64 + internal var sequence: Int64 = 0 + internal var lastTimestamp: Int64 = -1 + + public init(workerId: Int64, datacenterId: Int64) { + if (workerId > maxWorkerId || workerId < 0) { + throw IllegalArgumentException("workerId can't be greater than ${maxWorkerId} or less than 0") + } + + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw IllegalArgumentException("datacenterId can't be greater than ${maxWorkerId} or less than 0") + } + + this.workerId = workerId + this.datacenterId = datacenterId + } + + let mtx = ReentrantMutex() + + public func nextId() { + synchronized (mtx) { + var timestamp = timeGen() + + if (timestamp < lastTimestamp) { + throw IllegalStateException("Clock moved backwards. Refusing to generate id for ${lastTimestamp - timestamp} milliseconds") + } + + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask + + if (sequence == 0) { + timestamp = tilNextMillis(lastTimestamp) + } + } else { + sequence = 0 + } + + lastTimestamp = timestamp + + ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence + } + } + + internal func tilNextMillis(lastTimestamp: Int64) { + var timestamp = timeGen() + + while (timestamp <= lastTimestamp) { + timestamp = timeGen() + } + + timestamp + } + + internal func timeGen() { + DateTime.nowUTC().toUnixTimeStamp().toMilliseconds() + } +} diff --git a/src/snowflake/snowflake_test.cj b/src/snowflake/snowflake_test.cj new file mode 100644 index 0000000..9ab5368 --- /dev/null +++ b/src/snowflake/snowflake_test.cj @@ -0,0 +1,31 @@ +package snowflake4cj.snowflake + +import std.unittest.* +import std.unittest.testmacro.* + +@Test +func testSnowflake() { + let snowflake = Snowflake(0, 0) + var last = 0 + for (i in 0..10000) { + let new = snowflake.nextId() + @Expect(new > last) + } +} + +@Test +func testFailedWhenMachineIdIsInvalid() { + for (i in -1..50) { + for (j in -1..50) { + if (i < 0 || i > 31 || j < 0 || j > 31) { + @ExpectThrows[IllegalArgumentException]({ + Snowflake(i, j) + }) + } else { + let snowflake = Snowflake(i, j) + let id = snowflake.nextId() + @Expect(id > 0) + } + } + } +}