// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using NodaTime; namespace Foxnouns.Backend.Database; public class SnowflakeGenerator : ISnowflakeGenerator { /// /// Singleton instance of SnowflakeGenerator. Where possible, use an injected ISnowflakeGenerator instead /// (see IServiceCollection.AddSnowflakeGenerator). /// public static SnowflakeGenerator Instance { get; } = new(); private readonly byte _processId; private long _increment; public SnowflakeGenerator(int? processId = null) { processId ??= Environment.ProcessId; _processId = (byte)(processId % 32); _increment = Random.Shared.NextInt64() % 4096; } /// /// Generate a snowflake ID for the given timestamp. /// /// An optional timestamp. If null, use the current timestamp. /// A new snowflake ID. public Snowflake GenerateSnowflake(Instant? time = null) { time ??= SystemClock.Instance.GetCurrentInstant(); long increment = Interlocked.Increment(ref _increment); int threadId = Environment.CurrentManagedThreadId % 32; long timestamp = time.Value.ToUnixTimeMilliseconds() - Snowflake.Epoch; return (timestamp << 22) | (uint)(_processId << 17) | (uint)(threadId << 12) | (increment % 4096); } } public static class SnowflakeGeneratorServiceExtensions { public static IServiceCollection AddSnowflakeGenerator( this IServiceCollection services, int? processId = null ) => services.AddSingleton(new SnowflakeGenerator(processId)); }