ET 7.2框架学习(2)

编程入门 行业动态 更新时间:2024-10-24 22:20:52

ET 7.2<a href=https://www.elefans.com/category/jswz/34/1770644.html style=框架学习(2)"/>

ET 7.2框架学习(2)

打开ET.sln开始阅读源代码。我们先来学习客户端的代码,打开ET工程后,此时可以看到如下的目录结构:

其中,红色框圈起来的部分为我们平时开发时使用的工程,由于默认为打包模式,其工程并未生成和加载,故我们需要将开发模式打开,回到Unity中,在菜单栏选中ETChangeDefineAdd ENABLE_CODES,回到Visual Studio 2022中,这时项目会重新加载,在解决方案资源管理器窗口UnityEnableCodes下会加载工程。

老规矩先找函数入口,在运行指南中有讲到Scenes目录中的Init场景为启动场景,打开它可以在Global节点下看到其挂载了一个init.cs的脚本,这就是启动脚本,打开它,可以发现它是在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这个单例管理类的周期函数。

这里来重点解释一些功能,一些一看就懂的就不赘述了。我们先来看单例:

前面说过,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 方法释放资源,然后将单例对象设为 nullthis.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.csISingletonLateUpdate.csISingletonUpdate.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()函数中将单例都加入成员变量singletons栈中统一进行管理,另外,再根据是否为ISingletonAwake、ISingletonUpdate、ISingletonLateUpdate接口,分别放入不同的容器中进行调度管理。前面说过用Init这个脚本的生命周期函数驱动Game的生命周期函数,这里又用Game的生命周期函数驱动单例的生命周期函数,只要单例类再继承于ISingletonAwake、ISingletonUpdate、ISingletonLateUpdate接口,即可实现相应周期函数的调用。

可以搜索一下代码,找到一个例子,我找到一个Root类,其定义如下:

public class Root: Singleton<Root>, ISingletonAwake

其同时继承于Singleton抽象类和ISingletonAwake接口,其可以在初始化时调用周期函数Awake()。

单例搞懂了,我们再回过头看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(){// 将当前的同步上下文设置为 threadSynchronizationContextSynchronizationContext.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)

注意Options是在外面解析生成对象后再传入AddSingleton()函数的。和直接调用下面的函数不同,之前使用的都是下面的这个函数。

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

Options的功能主要是标识进程的一些信息,如进程类型,配置文件位置,进程编号,是否为开发版本,日志等级等等。

第二个单例是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,记录日志的,会根据前面Options单例配置的日志等级来记录日志,不多说。

        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){// 尝试从字典中获取对应类型的队列,如果没有则返回nullQueue<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);}}
}

第五个单例是IdGenerater,Id生成器,生成Id用的。里面有3种id结构体:实体id(IdStruct)、实体实例id(InstanceIdStruct)和单位id(UnitIdStruct),分别通过IdGenerater的成员函数:GenerateId()、GenerateInstanceId()和GenerateUnitId()进行生成。通过实体id可以查询InstanceID。

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); // 取出10bitreturn v;}}// 定义一个类,用于生成不同类型的IDpublic 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,避免为0this.lastInstanceIdTime = 1;}// 获取2020年开始到现在的秒数,并赋值给lastIdTime变量this.lastIdTime = TimeSince2020();if (this.lastIdTime <= 0){Log.Warning($"lastIdTime less than 0: {this.lastIdTime}");// 将lastIdTime设为1,避免为0this.lastIdTime = 1;}// 获取2020年开始到现在的秒数,并赋值给lastUnitIdTime变量this.lastUnitIdTime = TimeSince2020();if (this.lastUnitIdTime <= 0){Log.Warning($"lastUnitIdTime less than 0: {this.lastUnitIdTime}");// 将lastUnitIdTime设为1,避免为0this.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;}// 用于生成实例IDpublic long GenerateInstanceId(){uint time = TimeSinceThisYear();if (time > this.lastInstanceIdTime){// 如果当前时间大于上一次生成实例ID的时间// 更新上一次生成实例ID的时间this.lastInstanceIdTime = time;// 将每秒每个进程生成的实例ID数量重置为0this.instanceIdValue = 0;}else{// 将每秒每个进程生成的实例ID数量加1++this.instanceIdValue;if (this.instanceIdValue > IdGenerater.Mask18bit - 1) // 18bit{// 如果每秒每个进程生成的实例ID数量超过了18位的最大值++this.lastInstanceIdTime; // 借用下一秒// 将每秒每个进程生成的实例ID数量重置为0this.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();}// 定义一个方法,用于生成IDpublic long GenerateId(){// 获取2020年开始到现在的秒数uint time = TimeSince2020();if (time > this.lastIdTime){// 如果当前时间大于上一次生成ID的时间// 更新上一次生成ID的时间this.lastIdTime = time;// 将每秒每个进程生成的ID数量重置为0this.value = 0;}else{// 如果当前时间等于或小于上一次生成ID的时间// 将每秒每个进程生成的ID数量加1++this.value;if (value > ushort.MaxValue - 1){// 如果每秒每个进程生成的ID数量超过了16位的最大值// 将每秒每个进程生成的ID数量重置为0this.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();}// 定义一个方法,用于生成UnitIDpublic 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数量重置为0this.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();}}
}

更多推荐

ET 7.2框架学习(2)

本文发布于:2024-02-26 15:18:12,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1702976.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:框架

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!