Skip to content

Commit

Permalink
refactor: tweak (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
kra8 authored May 10, 2022
1 parent 19aa225 commit ac64044
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 60 deletions.
128 changes: 68 additions & 60 deletions src/Snowflake.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php declare(strict_types=1);
<?php

namespace Kra8\Snowflake;
declare(strict_types=1);

use Exception;
namespace Kra8\Snowflake;

class Snowflake
{
Expand All @@ -20,6 +20,8 @@ class Snowflake

protected const TIMEOUT = 1000;

protected const MAX_SEQUENCE = 4095;

/**
* The epoch time.
*
Expand Down Expand Up @@ -70,51 +72,69 @@ public function __construct(int $timestamp = null, int $workerId = 1, int $datac
$this->lastTimestamp = $this->epoch;
}

public function makeSequenceId(int $currentTime, int $max = self::MAX_SEQUENCE): int
{
if ($this->lastTimestamp === $currentTime) {
$this->sequence = $this->sequence + 1;
return $this->sequence;
}

$this->sequence = mt_rand(0, $max);
$this->lastTimestamp = $currentTime;
return $this->sequence;
}

/**
* Generate the 64bit unique id.
*
* @return integer
*
* @throws Exception
* @return int
*/
public function next()
public function id(): int
{
$timestamp = $this->timestamp();

// もし、今回生成したタイムスタンプが、前回生成したタイムスタンプより過去のものなら、待機する
// そもそもこのような状況が起こるのかは不明なので、不要かもしれない。
if ($timestamp < $this->lastTimestamp) {
$waitTime = $this->lastTimestamp - $timestamp;

// 待機時間がtimeout以上なら、例外を投げる
if ($waitTime > self::TIMEOUT) {
$errorLog = "[Timeout({self::TIMEOUT})] Couldn't generation snowflake id, os time is backwards. [last timestamp:" . $this->lastTimestamp ."]";
throw new Exception($errorLog);
}

// 待機時間がtimeout以下なら、待機して再生成
usleep($this->lastTimestamp - $timestamp);
return $this->next();
$currentTime = $this->timestamp();
while (($sequenceId = $this->makeSequenceId($currentTime)) > self::MAX_SEQUENCE) {
usleep(1);
$currentTime = $this->timestamp();
}

// タイムスタンプが同じなら、シーケンスをインクリメント
if ($timestamp === $this->lastTimestamp) {
$this->sequence = $this->sequence + 1;
// シーケンス番号が最大値を超えたら、待機して再生成
if ($this->sequence > 4095) {
usleep(1);
$this->sequence = 0;
return $this->next();
}
} else {
$this->sequence = mt_rand(0, 4095);
$this->lastTimestamp = $currentTime;
return $this->toSnowflakeId($currentTime - $this->epoch, $sequenceId);
}

/**
* Generate the 64bit unique id.
*
* @return int
*/
public function next(): int
{
return $this->id();
}

/**
* Create 53bit Id.
* timestamp_bits(41) + sequence_bits(12)
*
* @return int
*/
public function short(): int
{
$currentTime = $this->timestamp();
while (($sequenceId = $this->makeSequenceId($currentTime)) > self::MAX_SEQUENCE) {
usleep(1);
$currentTime = $this->timestamp();
}

$this->lastTimestamp = $timestamp;
return $this->toSnowflakeId($timestamp - $this->epoch, $this->sequence);
$this->lastTimestamp = $currentTime;
return $this->toShortflakeId($currentTime - $this->epoch, $sequenceId);
}

public function toSnowflakeId(int $currentTime, int $sequence)
public function toShortflakeId(int $currentTime, int $sequenceId)
{
return ($currentTime << self::SEQUENCE_BITS) | ($sequenceId);
}

public function toSnowflakeId(int $currentTime, int $sequenceId)
{
$workerIdLeftShift = self::SEQUENCE_BITS;
$datacenterIdLeftShift = self::WORKER_ID_BITS + self::SEQUENCE_BITS;
Expand All @@ -123,7 +143,17 @@ public function toSnowflakeId(int $currentTime, int $sequence)
return ($currentTime << $timestampLeftShift)
| ($this->datacenterId << $datacenterIdLeftShift)
| ($this->workerId << $workerIdLeftShift)
| ($sequence);
| ($sequenceId);
}

/**
* Return the now unixtime.
*
* @return int
*/
public function timestamp(): int
{
return (int) floor(microtime(true) * 1000);
}

public function parse(int $id): array
Expand Down Expand Up @@ -155,26 +185,4 @@ public function parse(int $id): array
'datetime' => $datetime,
];
}

/**
* Create 53bit Id.
* timestamp_bits(41) + sequence_bits(12)
*
* @return int
*/
public function short(): int
{
$parsed = $this->parse($this->next());
return $parsed['timestamp'] << 12 | $parsed['sequence'];
}

/**
* Return the now unixtime.
*
* @return int
*/
public function timestamp(): int
{
return floor(microtime(true) * 1000) | 0;
}
}
21 changes: 21 additions & 0 deletions tests/SnowflakeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ public function testNextId()
$this->assertTrue($timestamp - $now < 3);
}

public function testMultipleNextId()
{
$instance = app(Snowflake::class);

$ids = [];
foreach (range(0, 1000) as $_) {
$ids[] = $instance->next();
}
$this->assertTrue(array_unique($ids) === $ids);
}

public function testShortId()
{
$now = strtotime(date('Y-m-d H:i:s'));
Expand All @@ -29,4 +40,14 @@ public function testShortId()

$this->assertTrue($timestamp - $now < 3);
}

public function testMultipleShortId()
{
$instance = app(Snowflake::class);
$ids = [];
foreach (range(0, 1000) as $_) {
$ids[] = $instance->short();
}
$this->assertTrue(array_unique($ids) === $ids);
}
}

0 comments on commit ac64044

Please sign in to comment.