ET 7.2 프레임워크 학습(2)

ET.sln을 열어 소스 코드 읽기를 시작하세요. 먼저 클라이언트 코드를 알아보겠습니다. ET 프로젝트를 열면 다음과 같은 디렉터리 구조를 볼 수 있습니다.

그 중 빨간색 박스로 동그라미 친 부분은 우리가 일상 개발에 사용하는 프로젝트인데, 기본값이 패키징 모드이므로 프로젝트가 생성 및 로드되지 않으므로 개발 모드를 켜고 Unity로 돌아와 ET를 선택해야 합니다. → 메뉴 표시줄 에서 ChangeDefineENABLE_CODES 추가하고 Visual Studio 2022로 돌아가면 프로젝트가 다시 로드되고 솔루션 탐색기 창 UnityEnableCodes 에 프로젝트가 로드됩니다 .

기존 규칙은 함수 입구를 먼저 찾는 것인데, 운영 가이드에서는 Scenes 디렉토리에 있는 Init 씬이 시작 씬 이라고 나와 있는데, 열어보면 Global 아래에 init.cs 스크립트가 마운트되어 있는 것을 볼 수 있습니다. node.Startup 스크립트입니다. 열어보면 Unity.Loader 프로젝트 하위에 있음을 알 수 있으며 , 코드는 다음과 같습니다. (ET는 자세히 설명하지 않았으므로 이해하기 쉽게 여기에 추가합니다.)

using System;
using System.Threading;
using CommandLine;
using UnityEngine;

namespace ET
{
    // 客户端初始化脚本
    public class Init: MonoBehaviour
    {
        // 整个客户端的入口
        private void Start()
        {
            // 使当前游戏对象在加载新场景时不被销毁
            DontDestroyOnLoad(gameObject);

            // 为当前应用程序域添加未处理异常的事件处理器
            AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
            {
                // 记录异常信息到日志中
                Log.Error(e.ExceptionObject.ToString());
            };
            
            // 添加线程同步处理功能
            Game.AddSingleton<MainThreadSynchronizationContext>();

            // 命令行参数
            // 使用 CommandLineParser 来标准化地解析命令行
            // CommandLineParser 是一款用于解析命令行参数的 NuGet 包。
            // 我们可以将下面的参数改为string[] args = "--AppType GameTool --StartConfig StartConfig/Localhost --Process 2 --Develop 1 --LogLevel 2 --Console 1 --CreateScenes 0".Split(" ");
            string[] args = "".Split(" ");
            Parser.Default.ParseArguments<Options>(args)                                       // 将args数组中的元素解析为Options类型的对象
                .WithNotParsed(error => throw new Exception($"命令行格式错误! {error}"))       // 如果解析失败,调用WithNotParsed方法,抛出一个异常,异常信息包含错误原因
                .WithParsed(Game.AddSingleton);                                                // 如果解析成功,调用WithParsed方法,将解析得到的Options对象作为参数传递给Game.AddSingleton方法

            Game.AddSingleton<TimeInfo>();
            Game.AddSingleton<Logger>().ILog = new UnityLogger();
            Game.AddSingleton<ObjectPool>();
            Game.AddSingleton<IdGenerater>();
            Game.AddSingleton<EventSystem>();
            Game.AddSingleton<TimerComponent>();
            Game.AddSingleton<CoroutineLockComponent>();
            
            ETTask.ExceptionHandler += Log.Error;

            Game.AddSingleton<CodeLoader>().Start();
        }

        /// <summary>
        /// 每帧调用一次
        /// </summary>
        private void Update()
        {
            // 更新游戏中的单例对象
            Game.Update();
        }

        /// <summary>
        /// 每帧结束时调用一次
        /// </summary>
        private void LateUpdate()
        {
            // 更新游戏中的单例对象
            Game.LateUpdate();
            Game.FrameFinishUpdate();
        }

        /// <summary>
        /// 应用程序退出
        /// </summary>
        private void OnApplicationQuit()
        {
            // 销毁单例对象
            Game.Close();
        }
    }
}

스크립트가 주로 다음 작업을 수행하는 것을 볼 수 있습니다.

  • 스크립트는 지속적이며 새 장면을 로드해도 무효화되지 않습니다.
  • 프로그램에서 예외가 발생하면 기록합니다.
  • 명령 매개변수가 구문 분석되었습니다.
  • 각각 다른 기능을 가진 많은 싱글톤이 추가되었습니다.
  • Unity의 수명 주기 기능을 사용하여 게임 싱글톤 관리 클래스의 주기적인 기능을 구동합니다.

여기서는 몇 가지 기능에 대해 중점적으로 설명하고, 한눈에 이해하기 쉬운 일부 기능에 대해서는 자세히 설명하지 않겠습니다. 먼저 싱글톤을 살펴보겠습니다.

앞에서 언급했듯이 Game은 싱글톤을 관리하는 데 사용되는 정적 클래스 입니다 . 위 코드는 다음과 같은 정적 템플릿 함수를 호출하여 많은 싱글톤을 추가합니다.

public static T AddSingleton<T>() where T: Singleton<T>, new()
{
    T singleton = new T();
    AddSingleton(singleton);
    return singleton;
}

T 유형은 추상 클래스 Singleton 의 하위 클래스 여야 합니다 . Singleton 클래스에 마우스를 놓고 F12 키 를 눌러 Singleton.cs 파일 로 이동합니다 .

using System;

namespace ET
{
    // 定义一个接口 ISingleton,用于实现单例模式
    public interface ISingleton : IDisposable
    {
        // 注册单例对象
        void Register();

        // 销毁单例对象
        void Destroy();

        // 判断单例对象是否已经被销毁
        bool IsDisposed();
    }

    // 定义一个抽象类 Singleton<T>,继承自 ISingleton 接口,用于创建泛型的单例对象
    public abstract class Singleton<T> : ISingleton where T : Singleton<T>, new()
    {
        // 单例对象是否已经被销毁
        private bool isDisposed;

        // 存储单例对象的实例
        [StaticField]
        private static T instance;

        // 一个静态属性,用于获取或设置单例对象的实例
        public static T Instance
        {
            get
            {
                return instance;
            }
        }

        // 实现 ISingleton 接口的 Register 方法,用于注册单例对象
        void ISingleton.Register()
        {
            // 如果单例对象已经存在,则抛出异常
            if (instance != null)
            {
                throw new Exception($"singleton register twice! {typeof(T).Name}");
            }

            // 否则将当前对象赋值给单例对象
            instance = (T)this;
        }

        // 实现 ISingleton 接口的 Destroy 方法,用于销毁单例对象
        void ISingleton.Destroy()
        {
            // 如果单例对象已经被销毁,则直接返回
            if (this.isDisposed)
            {
                return;
            }

            // 否则将 isDisposed 设为 true,并调用 Dispose 方法释放资源,然后将单例对象设为 null
            this.isDisposed = true;

            instance.Dispose();
            instance = null;
        }

        // 实现 ISingleton 接口的 IsDisposed 方法,用于判断单例对象是否已经被销毁
        bool ISingleton.IsDisposed()
        {
            return this.isDisposed;
        }

        // 定义一个虚方法 Dispose,用于释放资源,可以在子类中重写
        public virtual void Dispose()
        {
        }
    }
}

싱글톤 개체의 등록 및 삭제를 실현하기 위해 싱글톤 인터페이스 ISingleton 과 싱글톤 추상 클래스 Singleton을 정의합니다.

Singleton.cs 가 있는 Singleton 디렉터리를 엽니다 . ISingletonAwake.cs , ISingletonLateUpdate.cs , ISingletonUpdate.cs 파일이 여러 개 있는데 각각 ISingletonUpdate 인터페이스 , ISingletonLateUpdate 인터페이스ISingletonUpdate 인터페이스를 정의하는 것을 볼 수 있습니다. 이러한 인터페이스의 기능은 아래에 나와 있습니다. . 싱글톤을 관리하는 데 사용되는 Game.cs 파일도 다음 내용과 함께 동일한 디렉터리에 있음을 확인할 수 있습니다.

using System;
using System.Collections.Generic;

namespace ET
{
    public static class Game
    {
        /// <summary>
        /// 定义一个私有静态只读字典,用来存储单例对象的类型和实例
        /// </summary>
        [StaticField]
        private static readonly Dictionary<Type, ISingleton> singletonTypes = new Dictionary<Type, ISingleton>();

        /// <summary>
        /// 定义一个私有静态只读栈,用来存储单例对象的顺序
        /// </summary>
        [StaticField]
        private static readonly Stack<ISingleton> singletons = new Stack<ISingleton>();

        /// <summary>
        /// 定义一个私有静态只读队列,用来存储需要更新的单例对象
        /// </summary>
        [StaticField]
        private static readonly Queue<ISingleton> updates = new Queue<ISingleton>();

        /// <summary>
        /// 定义一个私有静态只读队列,用来存储需要延迟更新的单例对象
        /// </summary>
        [StaticField]
        private static readonly Queue<ISingleton> lateUpdates = new Queue<ISingleton>();

        /// <summary>
        /// 定义一个私有静态只读队列,用来存储等待帧结束的任务
        /// </summary>
        [StaticField]
        private static readonly Queue<ETTask> frameFinishTask = new Queue<ETTask>();

        /// <summary>
        /// 用来创建和注册一个泛型参数T类型的单例对象,T必须继承自Singleton<T>并且有无参构造函数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static T AddSingleton<T>() where T: Singleton<T>, new()
        {
            T singleton = new T();
            AddSingleton(singleton);
            return singleton;
        }

        /// <summary>
        /// 用来注册一个已经创建好的单例对象
        /// </summary>
        /// <param name="singleton"></param>
        /// <exception cref="Exception"></exception>
        public static void AddSingleton(ISingleton singleton)
        {
            Type singletonType = singleton.GetType();
            if (singletonTypes.ContainsKey(singletonType))
            {
                // 抛出异常,表示已经存在该类型的单例对象
                throw new Exception($"already exist singleton: {singletonType.Name}");
            }

            // 否则将该类型和单例对象添加到字典中
            singletonTypes.Add(singletonType, singleton);

            // 将单例对象压入栈中
            singletons.Push(singleton);

            // 调用单例对象的Register方法,进行注册
            singleton.Register();

            // 如果单例对象实现了ISingletonAwake接口
            if (singleton is ISingletonAwake awake)
            {
                // 调用Awake方法,用来执行一些初始化逻辑
                awake.Awake();
            }

            // 如果单例对象实现了ISingletonUpdate接口
            if (singleton is ISingletonUpdate)
            {
                // 将单例对象入队到更新队列中
                updates.Enqueue(singleton);
            }

            // 如果单例对象实现了ISingletonLateUpdate接口
            if (singleton is ISingletonLateUpdate)
            {
                // 将单例对象入队到延迟更新队列中
                lateUpdates.Enqueue(singleton);
            }
        }

        /// <summary>
        /// 定义一个静态异步方法WaitFrameFinish,用来等待当前帧结束,并返回一个ETTask类型的任务
        /// </summary>
        /// <returns></returns>
        public static async ETTask WaitFrameFinish()
        {
            ETTask task = ETTask.Create(true);
            frameFinishTask.Enqueue(task);
            await task;
        }

        /// <summary>
        /// 用来执行更新队列中的所有单例对象
        /// </summary>
        public static void Update()
        {
            int count = updates.Count;
            while (count-- > 0)
            {
                // 从更新队列中取出一个单例对象
                ISingleton singleton = updates.Dequeue();

                if (singleton.IsDisposed())
                {
                    continue;
                }

                // 如果该单例对象不实现ISingletonUpdate接口,跳过本次循环
                if (singleton is not ISingletonUpdate update)
                {
                    continue;
                }

                // 将该单例对象重新加入到更新队列中
                updates.Enqueue(singleton);
                try
                {
                    // 调用该单例对象的Update方法
                    update.Update();
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        /// <summary>
        /// 用来执行延迟更新队列中的所有单例对象
        /// </summary>
        public static void LateUpdate()
        {
            int count = lateUpdates.Count;
            while (count-- > 0)
            {
                // 从延迟更新队列中取出一个单例对象
                ISingleton singleton = lateUpdates.Dequeue();
                
                if (singleton.IsDisposed())
                {
                    continue;
                }

                // 如果该单例对象不实现ISingletonLateUpdate接口,跳过本次循环
                if (singleton is not ISingletonLateUpdate lateUpdate)
                {
                    continue;
                }

                // 将该单例对象重新加入到延迟更新队列中
                lateUpdates.Enqueue(singleton);
                try
                {
                    // 调用该单例对象的LateUpdate方法
                    lateUpdate.LateUpdate();
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        /// <summary>
        /// 用来执行帧结束任务队列中的所有任务
        /// </summary>
        public static void FrameFinishUpdate()
        {
            while (frameFinishTask.Count > 0)
            {
                // 从帧结束任务队列中取出一个任务
                ETTask task = frameFinishTask.Dequeue();

                // 设置该任务的结果为成功
                task.SetResult();
            }
        }

        /// <summary>
        /// // 用来关闭所有的单例对象并清理相关数据结构
        /// </summary>
        public static void Close()
        {
            // 顺序反过来清理
            while (singletons.Count > 0)
            {
                ISingleton iSingleton = singletons.Pop();
                iSingleton.Destroy();
            }
            singletonTypes.Clear();
        }
    }
}

AddSingleton() 함수에서 싱글톤을 멤버 변수 싱글톤 스택에 추가하여 통합 관리하는 것을 볼 수 있으며, ISingletonAwake, ISingletonUpdate, ISingletonLateUpdate 인터페이스 여부에 따라 서로 다른 컨테이너에 배치되어 스케줄링 관리되는 것을 볼 수 있습니다. 앞서 언급한 바와 같이 Init 스크립트의 생명주기 기능은 Game의 생명주기 기능을 구동하는데 사용되는데 여기서는 Game의 생명주기 함수를 사용하여 싱글톤의 생명주기 기능을 구동시킨다. 클래스는 ISingletonAwake, ISingletonUpdate 및 ISingletonLateUpdate 인터페이스에서 상속됩니다. 해당 주기적 함수 호출을 구현합니다.

코드를 검색하여 예제를 찾을 수 있습니다. 다음 정의가 포함된 Root 클래스를 찾았습니다.

public class Root: Singleton<Root>, ISingletonAwake

이는 초기화 중에 주기 함수 Awake()를 호출할 수 있는 Singleton 추상 클래스와 ISingletonAwake 인터페이스 모두에서 상속됩니다.

이제 싱글톤을 이해했으므로 다시 Init.cs 초기화 스크립트의 기능을 살펴보겠습니다.

첫 번째 싱글톤은 MainThreadSynchronizationContext 이며, 그 기능은 스레드 동기화 기능을 구현하고 하위 스레드의 위임을 메인 스레드에 전달하여 스레드 간 리소스 경쟁을 방지하는 것이며 구현을 위해 동기화 큐를 사용합니다.

using System;
using System.Threading;

namespace ET
{

    public class MainThreadSynchronizationContext: Singleton<MainThreadSynchronizationContext>, ISingletonUpdate
    {
        // 创建一个 ThreadSynchronizationContext 类型的字段,用于管理线程同步队列
        private readonly ThreadSynchronizationContext threadSynchronizationContext = new ThreadSynchronizationContext();

        public MainThreadSynchronizationContext()
        {
            // 将当前的同步上下文设置为 threadSynchronizationContext
            SynchronizationContext.SetSynchronizationContext(this.threadSynchronizationContext);
        }
        
        public void Update()
        {
            // 取出队列中的Action执行
            this.threadSynchronizationContext.Update();
        }

        // 将一个委托和一个状态对象加入到线程同步队列中
        public void Post(SendOrPostCallback callback, object state)
        {
            this.Post(() => callback(state));
        }

        // 将一个委托加入到线程同步队列中
        public void Post(Action action)
        {
            this.threadSynchronizationContext.Post(action);
        }
    }
}

첫 번째 싱글톤은 명령줄 매개변수 구문 분석에 숨겨진 Options 입니다. 제가 작성한 코드 주석을 볼 수 있습니다. 명령 매개변수 구문 분석이 성공한 후 인스턴스를 추가하는 또 다른 오버로드된 함수가 호출됩니다.

public static void AddSingleton(ISingleton singleton)

옵션은 외부에서 개체를 구문 분석하고 생성한 후 AddSingleton() 함수에 전달됩니다 . 아래 함수를 직접 호출하는 것과는 달리 이전에는 다음 함수를 사용했습니다.

public static T AddSingleton<T>() where T: Singleton<T>, new()

옵션의 기능은 주로 프로세스 유형, 구성 파일 위치, 프로세스 번호, 개발 버전인지 여부, 로그 수준 등과 같은 프로세스의 일부 정보를 식별하는 것입니다.

두 번째 싱글턴은 TimeInfo 로, 시간대, 클라이언트 시간, 서버 시간, 프레임 시간 등 시간 정보와 관련된 함수를 가지고 있습니다. 함수는 비교적 간단하므로 자세히 설명하지 않겠습니다.

using System;

namespace ET
{
    /// <summary>
    /// 时间信息类
    /// </summary>
    public class TimeInfo: Singleton<TimeInfo>, ISingletonUpdate
    {
        /// <summary>
        /// 时区
        /// </summary>
        private int timeZone;
        
        public int TimeZone
        {
            get
            {
                return this.timeZone;
            }
            set
            {
                this.timeZone = value;
                dt = dt1970.AddHours(TimeZone);
            }
        }

        /// <summary>
        /// 表示1970年1月1日0点0分0秒的UTC时间
        /// </summary>
        private DateTime dt1970;

        /// <summary>
        /// 表示1970年1月1日0点0分0秒加上时区的时间
        /// </summary>
        private DateTime dt;

        /// <summary>
        /// 表示服务器时间和客户端时间的差值,只能在类内部获取,在类外部设置
        /// </summary>
        public long ServerMinusClientTime { private get; set; }

        /// <summary>
        /// 帧时间
        /// </summary>
        public long FrameTime;

        public TimeInfo()
        {
            // 初始化时间
            this.dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            this.dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

            // 初始化FrameTime的值,调用ClientNow方法获取当前客户端时间
            this.FrameTime = this.ClientNow();
        }

        public void Update()
        {
            this.FrameTime = this.ClientNow();
        }
        
        /// <summary> 
        /// 根据时间戳获取时间 
        /// </summary>  
        public DateTime ToDateTime(long timeStamp)
        {
            return dt.AddTicks(timeStamp * 10000);
        }

        /// <summary>
        /// 线程安全
        /// 获取当前客户端时间
        /// </summary>
        /// <returns></returns>
        public long ClientNow()
        {
            return (DateTime.UtcNow.Ticks - this.dt1970.Ticks) / 10000;
        }

        /// <summary>
        /// 获取当前服务器时间
        /// </summary>
        /// <returns></returns>
        public long ServerNow()
        {
            return ClientNow() + Instance.ServerMinusClientTime;
        }

        /// <summary>
        /// 获取当前客户端帧时间
        /// </summary>
        /// <returns></returns>
        public long ClientFrameTime()
        {
            return this.FrameTime;
        }

        /// <summary>
        /// 获取当前服务器帧时间
        /// </summary>
        /// <returns></returns>
        public long ServerFrameTime()
        {
            return this.FrameTime + Instance.ServerMinusClientTime;
        }

        /// <summary>
        /// 将一个DateTime对象转换为对应的时间戳
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public long Transition(DateTime d)
        {
            return (d.Ticks - dt.Ticks) / 10000;
        }
    }
}

세 번째 싱글톤은 이전 옵션 싱글톤에서 구성한 로그 수준에 따라 로그를 기록하는 Logger 입니다 .

        private const int TraceLevel = 1;           // 定义Trace级别的常量
        private const int DebugLevel = 2;           // 定义Debug级别的常量
        private const int InfoLevel = 3;            // 定义Info级别的常量
        private const int WarningLevel = 4;         // 定义Warning级别的常量

네 번째 싱글턴은 효율성을 높이기 위해 객체를 재사용하는 데 사용되는 객체 풀인 ObjectPool 입니다. 비교적 간단하므로 자세히 설명하지 않겠습니다.

using System;
using System.Collections.Generic;

namespace ET
{
    // 对象池
    public class ObjectPool : Singleton<ObjectPool>
    {
        // 定义一个字典,用于存储不同类型的对象队列
        private readonly Dictionary<Type, Queue<object>> pool = new Dictionary<Type, Queue<object>>();

        // 定义一个泛型方法,用于从对象池中获取指定类型的对象
        public T Fetch<T>() where T : class
        {
            return this.Fetch(typeof(T)) as T;
        }

        // 从对象池中获取指定类型的对象
        public object Fetch(Type type)
        {
            // 尝试从字典中获取对应类型的队列,如果没有则返回null
            Queue<object> queue = null;
            if (!pool.TryGetValue(type, out queue))
            {
                // 如果没有对应类型的队列,就创建一个新的对象并返回
                return Activator.CreateInstance(type);
            }

            // 如果有对应类型的队列,但是队列为空,也创建一个新的对象并返回
            if (queue.Count == 0)
            {
                return Activator.CreateInstance(type);
            }
            
            // 如果有对应类型的队列,并且队列不为空,就从队列中取出一个对象并返回
            return queue.Dequeue();
        }

        // 将对象回收到对象池中
        public void Recycle(object obj)
        {
            // 尝试从字典中获取对应类型的队列,如果没有则创建一个新的队列并添加到字典中
            Type type = obj.GetType();
            Queue<object> queue = null;
            if (!pool.TryGetValue(type, out queue))
            {
                queue = new Queue<object>();
                pool.Add(type, queue);
            }

            // 如果队列中的对象数量超过1000个,就不再回收该对象
            if (queue.Count > 1000)
            {
                return;
            }
            
            // 将对象加入到队列中
            queue.Enqueue(obj);
        }
    }
}

다섯 번째 싱글톤은 ID를 생성하는 데 사용되는 ID 생성기인 IdGenerater 입니다. id 구조에는 엔터티 ID(IdStruct), 엔터티 인스턴스 ID(InstanceIdStruct) 및 유닛 ID(UnitIdStruct)의 세 가지 유형이 있으며, 이는 각각 IdGenerater의 멤버 함수인 GenerateId(), generateInstanceId() 및 GenerateUnitId()를 통해 생성됩니다. InstanceID는 엔터티 ID를 통해 쿼리할 수 있습니다.

using System;
using System.Runtime.InteropServices;

namespace ET
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct IdStruct
    {
        public uint Time;    // 30bit,表示从2020年开始的秒数,最多可用34年
        public int Process;  // 18bit,表示进程号,最多可有262144个进程
        public ushort Value; // 16bit,表示每秒每个进程生成的ID数量,最多可有65536个

        // 将ID转换为长整型
        public long ToLong()
        {
            ulong result = 0;
            result |= this.Value;
            result |= (ulong) this.Process << 16;
            result |= (ulong) this.Time << 34;
            return (long) result;
        }

        // 根据时间、进程和值创建一个ID结构体
        public IdStruct(uint time, int process, ushort value)
        {
            this.Process = process;
            this.Time = time;
            this.Value = value;
        }

        // 根据长整型创建一个ID结构体
        public IdStruct(long id)
        {
            ulong result = (ulong) id; 
            this.Value = (ushort) (result & ushort.MaxValue);
            result >>= 16;
            this.Process = (int) (result & IdGenerater.Mask18bit);
            result >>= 18;
            this.Time = (uint) result;
        }

        // 重写ToString方法,用于返回ID的字符串表示
        public override string ToString()
        {
            return $"process: {this.Process}, time: {this.Time}, value: {this.Value}";
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct InstanceIdStruct
    {
        public uint Time;   // 当年开始的tick 28bit,表示从当年开始的秒数,最多可用8.7年
        public int Process; // 18bit,表示进程号,最多可有262144个进程
        public uint Value;  // 18bit,表示每秒每个进程生成的实例ID数量,最多可有262144个

        // 定义一个方法,用于将实例ID转换为长整型
        public long ToLong()
        {
            ulong result = 0;
            result |= this.Value;
            result |= (ulong)this.Process << 18;
            result |= (ulong) this.Time << 36;
            return (long) result;
        }

        // 根据长整型创建一个实例ID结构体
        public InstanceIdStruct(long id)
        {
            ulong result = (ulong) id;
            this.Value = (uint)(result & IdGenerater.Mask18bit);
            result >>= 18;
            this.Process = (int)(result & IdGenerater.Mask18bit);
            result >>= 18;
            this.Time = (uint)result;
        }

        // 根据时间、进程和值创建一个实例ID结构体
        public InstanceIdStruct(uint time, int process, uint value)
        {
            this.Time = time;
            this.Process = process;
            this.Value = value;
        }

        // 给SceneId使用,只包含进程和值两个字段
        public InstanceIdStruct(int process, uint value)
        {
            this.Time = 0;
            this.Process = process;
            this.Value = value;
        }

        // 重写ToString方法,用于返回实例ID的字符串表示
        public override string ToString()
        {
            return $"process: {this.Process}, value: {this.Value} time: {this.Time}";
        }
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct UnitIdStruct
    {
        public uint Time;        // 30bit,表示从2020年开始的秒数,最多可用34年
        public ushort Zone;      // 10bit,表示区域号,最多可有1024个区域
        public byte ProcessMode; // 8bit  Process % 256,表示进程号对256取余的结果,一个区域最多256个进程
        public ushort Value;     // 16bit 表示每秒每个进程生成的Unit数量,最多可有65536个

        // 将UnitID转换为长整型
        public long ToLong()
        {
            ulong result = 0;
            result |= this.Value;
            result |= (uint)this.ProcessMode << 16;
            result |= (ulong) this.Zone << 24;
            result |= (ulong) this.Time << 34;
            return (long) result;
        }

        // 根据区域、进程、时间和值创建一个UnitID结构体
        public UnitIdStruct(int zone, int process, uint time, ushort value)
        {
            this.Time = time;
            this.ProcessMode = (byte)(process % 256);
            this.Value = value;
            this.Zone = (ushort)zone;
        }

        // 根据长整型创建一个UnitID结构体
        public UnitIdStruct(long id)
        {
            ulong result = (ulong) id;
            this.Value = (ushort)(result & ushort.MaxValue);
            result >>= 16;
            this.ProcessMode = (byte)(result & byte.MaxValue);
            result >>= 8;
            this.Zone = (ushort)(result & 0x03ff);
            result >>= 10;
            this.Time = (uint)result;
        }

        // 重写ToString方法,用于返回UnitID的字符串表示
        public override string ToString()
        {
            return $"ProcessMode: {this.ProcessMode}, value: {this.Value} time: {this.Time}";
        }

        // 从UnitID中获取区域号
        public static int GetUnitZone(long unitId)
        {
            int v = (int) ((unitId >> 24) & 0x03ff); // 取出10bit
            return v;
        }
    }

    // 定义一个类,用于生成不同类型的ID
    public class IdGenerater: Singleton<IdGenerater>
    {
        public const int Mask18bit = 0x03ffff;   // 定义一个常量,用于表示18位的掩码

        public const int MaxZone = 1024;         // 定义一个常量,用于表示最大的区域数

        private long epoch2020;                  // 用于存储2020年开始的毫秒数
        private ushort value;                    // 用于存储每秒每个进程生成的ID数量
        private uint lastIdTime;                 // 用于存储上一次生成ID的时间

        private long epochThisYear;              // 用于存储当年开始的毫秒数
        private uint instanceIdValue;            // 用于存储每秒每个进程生成的实例ID数量
        private uint lastInstanceIdTime;         // 用于存储上一次生成实例ID的时间

        private ushort unitIdValue;              // 用于存储每秒每个进程生成的Unit数量
        private uint lastUnitIdTime;             // 用于存储上一次生成UnitID的时间

        public IdGenerater()
        {
            // 计算1970年开始的毫秒数
            long epoch1970tick = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000;

            // 计算2020年开始的毫秒数
            this.epoch2020 = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - epoch1970tick;

            // 计算当年开始的毫秒数
            this.epochThisYear = new DateTime(DateTime.Now.Year, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks / 10000 - epoch1970tick;

            // 获取当年开始到现在的秒数,并赋值给lastInstanceIdTime变量
            this.lastInstanceIdTime = TimeSinceThisYear();
            if (this.lastInstanceIdTime <= 0)
            {
                Log.Warning($"lastInstanceIdTime less than 0: {this.lastInstanceIdTime}");

                // 将lastInstanceIdTime设为1,避免为0
                this.lastInstanceIdTime = 1;
            }

            // 获取2020年开始到现在的秒数,并赋值给lastIdTime变量
            this.lastIdTime = TimeSince2020();
            if (this.lastIdTime <= 0)
            {
                Log.Warning($"lastIdTime less than 0: {this.lastIdTime}");

                // 将lastIdTime设为1,避免为0
                this.lastIdTime = 1;
            }

            // 获取2020年开始到现在的秒数,并赋值给lastUnitIdTime变量
            this.lastUnitIdTime = TimeSince2020();
            if (this.lastUnitIdTime <= 0)
            {
                Log.Warning($"lastUnitIdTime less than 0: {this.lastUnitIdTime}");

                // 将lastUnitIdTime设为1,避免为0
                this.lastUnitIdTime = 1;
            }
        }

        // 计算从2020年开始到当前帧时间的秒数
        private uint TimeSince2020()
        {
            uint a = (uint)((TimeInfo.Instance.FrameTime - this.epoch2020) / 1000);
            return a;
        }
        
        // 计算从当年开始到当前帧时间的秒数
        private uint TimeSinceThisYear()
        {
            uint a = (uint)((TimeInfo.Instance.FrameTime - this.epochThisYear) / 1000);
            return a;
        }

        // 用于生成实例ID
        public long GenerateInstanceId()
        {
            uint time = TimeSinceThisYear();

            if (time > this.lastInstanceIdTime)
            {
                // 如果当前时间大于上一次生成实例ID的时间

                // 更新上一次生成实例ID的时间
                this.lastInstanceIdTime = time;

                // 将每秒每个进程生成的实例ID数量重置为0
                this.instanceIdValue = 0;
            }
            else
            {
                // 将每秒每个进程生成的实例ID数量加1
                ++this.instanceIdValue;
                
                if (this.instanceIdValue > IdGenerater.Mask18bit - 1) // 18bit
                {
                    // 如果每秒每个进程生成的实例ID数量超过了18位的最大值
                    ++this.lastInstanceIdTime; // 借用下一秒

                    // 将每秒每个进程生成的实例ID数量重置为0
                    this.instanceIdValue = 0;

                    Log.Error($"instanceid count per sec overflow: {time} {this.lastInstanceIdTime}");
                }
            }

            // 根据时间、进程和值创建一个实例ID结构体
            InstanceIdStruct instanceIdStruct = new InstanceIdStruct(this.lastInstanceIdTime, Options.Instance.Process, this.instanceIdValue);
            return instanceIdStruct.ToLong();
        }

        // 定义一个方法,用于生成ID
        public long GenerateId()
        {
            // 获取2020年开始到现在的秒数
            uint time = TimeSince2020();

            if (time > this.lastIdTime)
            {
                // 如果当前时间大于上一次生成ID的时间

                // 更新上一次生成ID的时间
                this.lastIdTime = time;

                // 将每秒每个进程生成的ID数量重置为0
                this.value = 0;
            }
            else
            {
                // 如果当前时间等于或小于上一次生成ID的时间

                // 将每秒每个进程生成的ID数量加1
                ++this.value;
                
                if (value > ushort.MaxValue - 1)
                {
                    // 如果每秒每个进程生成的ID数量超过了16位的最大值

                    // 将每秒每个进程生成的ID数量重置为0
                    this.value = 0;
                    ++this.lastIdTime; // 借用下一秒
                    Log.Error($"id count per sec overflow: {time} {this.lastIdTime}");
                }
            }

            // 根据时间、进程和值创建一个ID结构体
            IdStruct idStruct = new IdStruct(this.lastIdTime, Options.Instance.Process, value);
            return idStruct.ToLong();
        }

        // 定义一个方法,用于生成UnitID
        public long GenerateUnitId(int zone)
        {
            if (zone > MaxZone)
            {
                // 如果区域号大于最大区域数,说明出错了
                throw new Exception($"zone > MaxZone: {zone}");
            }

            // 获取2020年开始到现在的秒数
            uint time = TimeSince2020();

            if (time > this.lastUnitIdTime)
            {
                // 如果当前时间大于上一次生成UnitID的时间
                this.lastUnitIdTime = time;

                // 将每秒每个进程生成的Unit数量重置为0
                this.unitIdValue = 0;
            }
            else
            {
                // 如果当前时间等于或小于上一次生成UnitID的时间

                // 将每秒每个进程生成的Unit数量加1
                ++this.unitIdValue;
                
                if (this.unitIdValue > ushort.MaxValue - 1)
                {
                    // 如果每秒每个进程生成的Unit数量超过了16位的最大值

                    this.unitIdValue = 0;
                    ++this.lastUnitIdTime; // 借用下一秒
                    Log.Error($"unitid count per sec overflow: {time} {this.lastUnitIdTime}");
                }
            }

            // 创建一个UnitIdStruct结构体
            UnitIdStruct unitIdStruct = new UnitIdStruct(zone, Options.Instance.Process, this.lastUnitIdTime, this.unitIdValue);
            return unitIdStruct.ToLong();
        }
    }
}

추천

출처blog.csdn.net/u013404885/article/details/131257104