提交示例代码

This commit is contained in:
Bob.Song
2026-03-05 11:39:06 +08:00
commit 25958f58c3
2534 changed files with 209593 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
namespace Fantasy;
public class MailBoxManageComponentDestroySystem : DestroySystem<MailBoxManageComponent>
{
protected override void Destroy(MailBoxManageComponent self)
{
if (self.TimerId != 0)
{
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
}
foreach (var (_, mailBox) in self.MailBoxes)
{
mailBox.Dispose();
}
self.MinTime = 0;
self.MailsByAccount.Clear();
self.MailsByMailBoxType.Clear();
self.Timers.Clear();
self.TimeOutQueue.Clear();
}
}
public static class MailBoxManageComponentSystem
{
public static async FTask Init(this MailBoxManageComponent self)
{
// 获取数据库中所有的没有处理完成的MailBox
var mailBoxes = await self.Scene.World.DataBase.Query<MailBox>(d => true);
foreach (var mailBox in mailBoxes)
{
self.MailBoxes.Add(mailBox.Id, mailBox);
switch (mailBox.MailBoxType)
{
case MailBoxType.Specify:
{
foreach (var accountId in mailBox.AccountId)
{
self.MailsByAccount.Add(accountId, mailBox);
}
break;
}
case MailBoxType.All:
case MailBoxType.AllToDate:
{
self.MailsByMailBoxType.Add((int)mailBox.MailBoxType, mailBox);
break;
}
}
}
self.TimerId = self.Scene.TimerComponent.Net.RepeatedTimer(MailBoxManageComponent.MailCheckTIme, self.Timeout);
}
private static void Timeout(this MailBoxManageComponent self)
{
var currentTime = TimeHelper.Now;
if (currentTime < self.MinTime)
{
return;
}
// 检查当然时候有过期的邮件箱需要处理
foreach (var (timeKey, _) in self.Timers)
{
if (timeKey > currentTime)
{
self.MinTime = timeKey;
break;
}
self.TimeOutQueue.Enqueue(timeKey);
}
while (self.TimeOutQueue.TryDequeue(out var timeKey))
{
foreach (var mailBoxId in self.Timers[timeKey])
{
Log.Info($"MailBox:{mailBoxId} 过期了!");
self.Remove(mailBoxId).Coroutine();
}
self.Timers.RemoveKey(timeKey);
}
}
public static async FTask Remove(this MailBoxManageComponent self, long mailBoxId)
{
if (!self.MailBoxes.Remove(mailBoxId, out var mailBox))
{
return;
}
// 先删除按照分类存储的邮箱
self.MailsByMailBoxType.RemoveValue((int)mailBox.MailBoxType, mailBox);
// 删除个人邮件的邮件箱
self.MailsByAccount.RemoveValue(mailBoxId, mailBox);
// 在数据库中删除这个邮件
await self.Scene.World.DataBase.Remove<MailBox>(mailBoxId);
// 销毁这个MailBox
mailBox.Dispose();
}
public static async FTask GetHaveMail(this MailBoxManageComponent self, MailUnit mailUnit, bool sync)
{
var now = TimeHelper.Now;
var worldDataBase = self.Scene.World.DataBase;
var mailComponent = mailUnit.GetComponent<MailComponent>();
// 玩家领取范围邮件的逻辑处理
foreach (var (mailBoxType, mailBoxList) in self.MailsByMailBoxType)
{
using var removeMailBox = ListPool<MailBox>.Create();
switch ((MailBoxType)mailBoxType)
{
// 发送给所有人的邮件
case MailBoxType.All:
{
foreach (var mailBox in mailBoxList)
{
if (!mailBox.Received.Contains(mailUnit.Id))
{
// 如果是没有领取过这个邮件,首先要把自己添加到领取过的名单中。
mailBox.Received.Add(mailUnit.Id);
// 发送给自己的邮件列表里添加邮件。
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
}
if (mailBox.ExpireTime <= now)
{
// 邮件已经过期了,要进行清理这个邮件的操作了
removeMailBox.Add(mailBox);
continue;
}
// 保存邮件箱状态到数据库中。
await worldDataBase.Save(mailBox);
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
// 这里有一个小的细节要处理,这里大家课下自己实现一下。
break;
}
case MailBoxType.AllToDate:
{
foreach (var mailBox in mailBoxList)
{
Log.Debug($"mailBox.CreateTime >= mailUnit.CreateTime {mailBox.CreateTime} >= {mailUnit.CreateTime}");
if (mailBox.CreateTime >= mailUnit.CreateTime && !mailBox.Received.Contains(mailUnit.Id))
{
// 如果是没有领取过这个邮件,首先要把自己添加到领取过的名单中。
mailBox.Received.Add(mailUnit.Id);
// 发送给自己的邮件列表里添加邮件。
await mailUnit.GetComponent<MailComponent>().Add(MailFactory.Serializer.Clone(mailBox.Mail), true);
}
if (mailBox.ExpireTime <= now)
{
// 邮件已经过期了,要进行清理这个邮件的操作了
removeMailBox.Add(mailBox);
continue;
}
// 保存邮件箱状态到数据库中。
await worldDataBase.Save(mailBox);
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
break;
}
}
}
if (self.MailsByAccount.TryGetValue(mailUnit.AccountId, out var mailBoxes))
{
using var removeMailBox = ListPool<MailBox>.Create();
foreach (var mailBox in mailBoxes)
{
var cloneMail = MailFactory.Serializer.Clone(mailBox.Mail);
await mailComponent.Add(cloneMail, sync);
mailBox.AccountId.Remove(mailUnit.AccountId);
Log.Debug($"领取了一个离线邮件 MailId:{cloneMail.Id}");
if (mailBox.AccountId.Count <= 0)
{
// 当邮件箱里没有要发送的玩家了,就表示这个邮件箱已经没有用处,可以删除了。
removeMailBox.Add(mailBox);
}
}
foreach (var mailBox in removeMailBox)
{
await self.Remove(mailBox.Id);
}
if (mailBoxes.Count <= 0)
{
// 如果MailsByAccount里的邮件箱已经没有邮件了就删除这个邮件箱的缓存。
self.MailsByAccount.RemoveByKey(mailUnit.AccountId);
}
}
}
}

View File

@@ -0,0 +1,226 @@
using Fantasy.Async;
using Fantasy.DataStructure.Collection;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type.
namespace Fantasy;
public class MailComponentDestroySystem : DestroySystem<MailComponent>
{
protected override void Destroy(MailComponent self)
{
foreach (var (_, mail) in self.Mails)
{
mail.Dispose();
}
self.Mails.Clear();
self.Timer.Clear();
}
}
public class MailComponentDeserializeSystem : DeserializeSystem<MailComponent>
{
protected override void Deserialize(MailComponent self)
{
self.Timer.Clear();
foreach (var (_, mail) in self.Mails)
{
self.Timer.Add(mail.ExpireTime, mail.Id);
}
}
}
public static class MailComponentSystem
{
public static async FTask Add(this MailComponent self, Mail mail, bool sync)
{
mail.CreateTime = TimeHelper.Now;
mail.ExpireTime = mail.CreateTime + MailComponent.MailExpireTime;
// 如果身上的邮件数量,大于了设置的最大数量怎么办?
// 先不用考虑,稍后咱们再解决问题。
if (self.Mails.Count >= MailComponent.MaxMailCount)
{
// 删除最老的邮件。
var (_, value) = self.Timer.First();
var removeId = value[0];
await self.Remove(removeId, MailRemoveActionType.Overtop, sync);
}
self.Mails.Add(mail.Id, mail);
self.Timer.Add(mail.ExpireTime, mail.Id);
if (sync)
{
// 同步邮件状态给客户端。
self.Scene.NetworkMessagingComponent.SendInnerRoute(self.GetParent<MailUnit>().GateRouteId, new Mail2C_HaveMail()
{
Mail = mail.ToMailSimplifyInfo()
});
}
// 这里的保存,也可以不用,这里看情况而定。
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponentSystem Add {mail.Id} Count:{self.Mails.Count}");
}
public static async FTask<uint> Remove(this MailComponent self, long mailId, MailRemoveActionType mailRemoveActionType ,bool sync)
{
if (!self.Mails.Remove(mailId, out var mail))
{
// 这里的1代表的没有找到对应邮件。
return 1;
}
self.Timer.RemoveValue(mail.ExpireTime, mail.Id);
mail.Dispose();
if (sync)
{
// 同步给客户端,邮件删除的消息。
self.Scene.NetworkMessagingComponent.SendInnerRoute(self.GetParent<MailUnit>().GateRouteId,
new Mail2C_MailState()
{
MailState = (int)mailRemoveActionType, MailId = mailId
});
}
// 保存下数据库。
await self.Scene.World.DataBase.Save(self);
Log.Info($"MailComponentSystem Remove {mailId} mailRemoveActionType:{mailRemoveActionType} Count:{self.Mails.Count}");
return 0;
}
public static async FTask CheckTimeOut(this MailComponent self)
{
var now = TimeHelper.Now;
using var listPool = ListPool<long>.Create();
foreach (var (_, mail) in self.Mails)
{
if (mail.ExpireTime > now)
{
continue;
}
listPool.Add(mail.Id);
}
foreach (var mailId in listPool)
{
await self.Remove(mailId, MailRemoveActionType.ExpireTimeRemove,false);
}
Log.Info($"MailComponentSystem CheckTimeOut Count:{listPool.Count}");
}
public static async FTask<(uint ErrorCode, Mail mail)> OpenMail(this MailComponent self, long mailId)
{
if (!self.Mails.TryGetValue(mailId, out var mail))
{
// 这个1代表的是没有找到对应邮件
return (1, null);
}
if (mail.ExpireTime < TimeHelper.Now)
{
// 如果邮件已经过期了,需要清楚这个邮件。
await self.Remove(mailId, MailRemoveActionType.ExpireTimeRemove, true);
// 这里2代表的是邮件已经过期。
return (2, null);
}
mail.MailState = MailState.HaveRead;
// 这个保存数据库不是必须要保存的,因为一个邮件的查看状态并不能影响游戏的逻辑。
// 这里的话,大家看自己的需求而定。
await self.Scene.World.DataBase.Save(self);
return (0, mail);
}
public static void GetMailSimplifyInfos(this MailComponent self, ICollection<MailSimplifyInfo> mailSimplifyInfos)
{
foreach (var (_, mail) in self.Mails)
{
mailSimplifyInfos.Add(mail.ToMailSimplifyInfo());
}
}
public static void GetMailSimplifyInfos(this MailComponent self, ICollection<MailSimplifyInfo> mailSimplifyInfos, int pageIndex, int pageSize)
{
foreach (var (_, mail) in self.Mails.Skip((pageIndex -1) * pageSize).Take(pageSize))
{
mailSimplifyInfos.Add(mail.ToMailSimplifyInfo());
}
}
public static async FTask<uint> Receive(this MailComponent self, long mailId, bool money, List<long> item, bool sync)
{
if (!self.Mails.TryGetValue(mailId, out var mail))
{
// 这里的1代表是没有找到该邮件。
return 1;
}
var needSave = false;
if (money && mail.Money > 0)
{
// 领取金钱一般都是在玩家身上但现在咱们所在的服务器是Mail服务器玩家并不在这个服务器.
// 所以一般金钱的操作会有一个专用的接口来操作。这个接口无论在什么服务器上,都会正确的发送到用户所在的服务器上进行金钱操作。
// 假设下面的增加金钱的一个异步接口。
// var resposne = await MoneyHelper.Add(self.Scene, mail.Money, sync);
// if (resposne.ErrorCode != 0)
// {
// // 这里的2代表的是金钱添加失败。
// return 2;
// }
// 再假设增加金钱是成功的,那么咱们就要处理邮件相关信息了。
mail.Money = 0;
needSave = true;
}
if (item != null && item.Count > 0)
{
using var listItem = ListPool<Item>.Create();
foreach (var itemId in item)
{
var rItem = mail.Items.FirstOrDefault(d => d.Id == itemId);
if (rItem == null)
{
continue;
}
listItem.Add(rItem);
}
// 假设背包在其他服务器,需要调用某一个接口来添加物品到目标服务器的背包身上。
// var response = await BagHelper.Add(self.Scene, listItem, sync);
// if (response.ErrorCode != 0)
// {
// return 2;
// }
// 还有一种情况就是背包里的空间只有一个空余位置了也就是只能放进一个物品了但是邮件领取的是2个物品.
// 会有下面2中情况,当然这个情况是要策划来决定怎么处理:
// 1、只领取一个物品到背包中另外一个不领取然后提示用户背包已满。
// 2、一个都不领取直接提示用户背包已满。
// 如果是是第一种情况下把BagHelper.Add接口就需要返回添加成功的物品ID方便邮件来处理。
// 假设全部物品都领取成功了,就可以执行下面的逻辑了。
foreach (var item1 in listItem)
{
mail.Items.Remove(item1);
item1.Dispose();
}
needSave = true;
}
if (needSave)
{
await self.Scene.World.DataBase.Save(self);
}
return 0;
}
}

View File

@@ -0,0 +1,176 @@
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Entitas.Interface;
using Fantasy.Helper;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8601 // Possible null reference assignment.
namespace Fantasy;
public sealed class MailUnitManageComponentDestroySystem : DestroySystem<MailUnitManageComponent>
{
protected override void Destroy(MailUnitManageComponent self)
{
// 如果不是在.net8的环境下比如是Unity里。这个的foreach有可能会出现问题
// 问题的原因是有可能你在mailUnit组件的销毁的时候去UnitByAccountId删除。
// 这样就会出现了一个经典的错误就是无法再foreach里删除或改变元素。
foreach (var (_, mailUnit) in self.UnitByAccountId)
{
mailUnit.Dispose();
}
self.UnitByAccountId.Clear();
self.UnitByName.Clear();
self.Online.Clear();
}
}
public static class MailUnitManageComponentSystem
{
public static async FTask Init(this MailUnitManageComponent self)
{
var units = await self.Scene.World.DataBase.Query<MailUnit>(d => true, true);
foreach (var mailUnit in units)
{
self.UnitByName.Add(mailUnit.Name, mailUnit);
self.UnitByAccountId.Add(mailUnit.AccountId,mailUnit);
}
Log.Debug($"MailUnitManageComponent Init units:{units.Count}");
}
/// <summary>
/// 用户中心登录逻辑,如果有更新的数据,会自动更新。
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <param name="unitName"></param>
/// <param name="gateRouteId"></param>
/// <returns></returns>
public static async FTask<MailUnit> Online(this MailUnitManageComponent self, long accountId, string unitName, long gateRouteId)
{
var selfScene = self.Scene;
if (self.UnitByAccountId.TryGetValue(accountId, out var mailUnit))
{
Log.Debug($"用户已经存在行不需要重新创建 Name:{unitName} mailUnit:{mailUnit.RuntimeId}");
if (mailUnit.Name != unitName)
{
// 如果不一致的情况,要先删除之前的缓存,然后再添加到缓存。
self.UnitByName.Remove(mailUnit.Name);
self.UnitByName.Add(unitName, mailUnit);
mailUnit.Name = unitName;
}
}
else
{
// 创建新的MailUnit实体。
mailUnit = Entity.Create<MailUnit>(selfScene,accountId, true, true);
mailUnit.Name = unitName;
mailUnit.AccountId = accountId;
mailUnit.CreateTime = TimeHelper.Now;
// 把创建好的实体添加到缓存中,方便后面使用。
self.UnitByAccountId.Add(accountId, mailUnit);
self.UnitByName.Add(unitName,mailUnit);
Log.Debug($"用户不存在行需要创建 Name:{unitName} mailUnit:{mailUnit.RuntimeId}");
}
// 设置GateRouteId
mailUnit.GateRouteId = gateRouteId;
// 设置MailComponent
if (mailUnit.GetComponent<MailComponent>() == null)
{
// 数据库中查询这个组件是否存在。
var mailComponent = await selfScene.World.DataBase.Query<MailComponent>(mailUnit.Id, true);
if (mailComponent == null)
{
// MailUnit没有这个MailComponent组件就给添加一个新的组件。
mailUnit.AddComponent<MailComponent>();
}
else
{
mailUnit.AddComponent(mailComponent);
}
}
// 可选,如果你不需要保存到数据库,那么可以忽略下面的代码。
await selfScene.World.DataBase.Save(mailUnit);
// 领取下离线的邮件。如果有的情况下。
await selfScene.GetComponent<MailBoxManageComponent>().GetHaveMail(mailUnit, true);
// 把用户添加到在线的列表
self.Online.Add(mailUnit.Id, mailUnit);
// 返回MailUnit实体。
return mailUnit;
}
/// <summary>
/// 退出登录逻辑,会自动保存数据。
/// </summary>
/// <param name="self"></param>
/// <param name="mailUnit"></param>
public static async FTask Exit(this MailUnitManageComponent self, MailUnit mailUnit)
{
var mailComponent = mailUnit.GetComponent<MailComponent>();
if (mailComponent == null)
{
return;
}
// 保存mailComponent到数据库中。
await self.Scene.World.DataBase.Save(mailComponent);
// 移除这个组件。
mailUnit.RemoveComponent<MailComponent>();
// 在在线列表的容器中移除这个MailUnit实体。
self.Online.Remove(mailUnit.Id);
Log.Debug($"AccountId:{mailUnit.AccountId} Name:{mailUnit.Name} Exit!");
}
/// <summary>
/// 根据UnitName获取MailUnit实体
/// </summary>
/// <param name="self"></param>
/// <param name="unitName"></param>
/// <param name="mailUnit"></param>
/// <returns></returns>
public static bool TryGet(this MailUnitManageComponent self, string unitName, out MailUnit mailUnit)
{
return self.UnitByName.TryGetValue(unitName, out mailUnit);
}
/// <summary>
/// 根据AccountId获取MailUnit实体
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <param name="mailUnit"></param>
/// <returns></returns>
public static bool TryGet(this MailUnitManageComponent self, long accountId, out MailUnit mailUnit)
{
return self.UnitByAccountId.TryGetValue(accountId, out mailUnit);
}
/// <summary>
/// 删除MailUnit实体。
/// </summary>
/// <param name="self"></param>
/// <param name="accountId"></param>
/// <returns></returns>
public static async FTask<uint> Remove(this MailUnitManageComponent self, long accountId)
{
// if (!self.UnitByAccountId.TryGetValue(accountId, out var mailUnit))
// {
// // 这个1代表的是没有找到对应的MailUnit
// return 1;
// }
if (!self.UnitByAccountId.Remove(accountId, out var mailUnit))
{
// 这个1代表的是没有找到对应的MailUnit
return 1;
}
self.UnitByName.Remove(mailUnit.Name);
// 在数据库中删除MailUnit实体
await self.Scene.World.DataBase.Remove<MailUnit>(mailUnit.Id);
// 销毁这个MailUnit
mailUnit.Dispose();
return 0;
}
}