ET.sln을 열어 소스 코드 읽기를 시작하세요. 먼저 클라이언트 코드를 알아보겠습니다. ET 프로젝트를 열면 다음과 같은 디렉터리 구조를 볼 수 있습니다.
그 중 빨간색 박스로 동그라미 친 부분은 우리가 일상 개발에 사용하는 프로젝트인데, 기본값이 패키징 모드이므로 프로젝트가 생성 및 로드되지 않으므로 개발 모드를 켜고 Unity로 돌아와 ET를 선택해야 합니다. → 메뉴 표시줄 에서 ChangeDefine → ENABLE_CODES 추가하고 Visual Studio 2022로 돌아가면 프로젝트가 다시 로드되고 솔루션 탐색기 창 Unity → EnableCodes 에 프로젝트가 로드됩니다 .
기존 규칙은 함수 입구를 먼저 찾는 것인데, 운영 가이드에서는 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();
}
}
}