ET游戏框架整理笔记3: 常用内置组件功能
上一节说道 挂载组件的几个步骤
1. 组件工厂创建组件
2. 如果有泛型类以组件为参数 并且实现了IUpdateSystem接口的话会调用它的update方法
如下面这个类
[ObjectSystem]
public class TimerComponentUpdateSystem : UpdateSystem<TimerComponent>
{
public override void Update(TimerComponent self)
{
self.Update();
}
}
3. 组件添加到父组件字典中
-------------------------------------------------------上一节分界线-------------------------------------------------------
main函数中加载完dll就是 挂载这两个组件 OptionComponent StartConfigComponent
Options options = Game.Scene.AddComponent<OptionComponent, string[]>(args).Options;
StartConfig startConfig = Game.Scene.AddComponent<StartConfigComponent, string, int>(options.Config, options.AppId).StartConfig;
这两个组件干嘛用的 上一节有说道创建组件时候会判断 ,会把 dotnet后面的参数序列化到组件的options属性中
startconfig组件根据上面的文件路径加载配置 根据apptype初始化组件的各类配置文件 如下面这些
public StartConfig StartConfig { get; private set; }
public StartConfig DBConfig { get; private set; }
public StartConfig RealmConfig { get; private set; }
public StartConfig LocationConfig { get; private set; }
public List<StartConfig> MapConfigs { get; private set; }
public List<StartConfig> GateConfigs { get; private set; }
消息分发组件 MessageDispatcherComponent
[ObjectSystem]
public class MessageDispatcherComponentAwakeSystem : AwakeSystem<MessageDispatcherComponent>
{
public override void Awake(MessageDispatcherComponent self)
{
self.Load();
}
}
创建该组件时调用load方法
/// <summary>
/// 消息分发组件
/// </summary>
public static class MessageDispatcherComponentHelper
{
public static void Load(this MessageDispatcherComponent self)
{
self.Handlers.Clear();AppType appType = StartConfigComponent.Instance.StartConfig.AppType;
List<Type> types = Game.EventSystem.GetTypes(typeof(MessageHandlerAttribute));
foreach (Type type in types)
{
object[] attrs = type.GetCustomAttributes(typeof(MessageHandlerAttribute), false);
if (attrs.Length == 0)
{
continue;
}MessageHandlerAttribute messageHandlerAttribute = attrs[0] as MessageHandlerAttribute;
if (!messageHandlerAttribute.Type.Is(appType))
{
continue;
}IMHandler iMHandler = Activator.CreateInstance(type) as IMHandler;
if (iMHandler == null)
{
Log.Error($"message handle {type.Name} 需要继承 IMHandler");
continue;
}Type messageType = iMHandler.GetMessageType();
ushort opcode = Game.Scene.GetComponent<OpcodeTypeComponent>().GetOpcode(messageType);
if (opcode == 0)
{
Log.Error($"消息opcode为0: {messageType.Name}");
continue;
}
self.RegisterHandler(opcode, iMHandler);
}
}public static void RegisterHandler(this MessageDispatcherComponent self, ushort opcode, IMHandler handler)
{
if (!self.Handlers.ContainsKey(opcode))
{
self.Handlers.Add(opcode, new List<IMHandler>());
}
self.Handlers[opcode].Add(handler);
}}
}
这个方法会从所有组件中找到 标记有MessageHandlerAttribute的类遍历 => 反射创建该类的实例A => 获取该Message的类型B,如下图的R2C_Ping => 把B的编码作为key 实例A作为value 缓存到 ActorMessageDispatcherComponent 组件的ActorMessageHandlers字典中
如下面这个就是一个消息处理类
[MessageHandler(AppType.AllServer)]
public class C2R_PingHandler : AMRpcHandler<C2R_Ping, R2C_Ping>
{
protected override async ETTask Run(Session session, C2R_Ping request, R2C_Ping response, Action reply)
{
reply();
await ETTask.CompletedTask;
}
}
缓存的这些消息处理类在后面收发协议时候回用到
内网通信组件 NetInnerComponent
顾名思义 这个是内网通信用的 添加组件时候传入了内网的地址
Game.Scene.AddComponent<NetInnerComponent, string>(innerConfig.Address);
添加组件后会自动调用awake方法 看一下这个方法干了啥
public static void Awake(this NetInnerComponent self, string address)
{
self.Awake(NetworkProtocol.TCP, address, Packet.PacketSizeLength4);
self.MessagePacker = new MongoPacker();
self.MessageDispatcher = new InnerMessageDispatcher();
self.AppType = StartConfigComponent.Instance.StartConfig.AppType;
}
标黄的方法是父组件 NetworkComponent的方法 根据协议类型创建不同的通信类
public void Awake(NetworkProtocol protocol, string address, int packetSize = Packet.PacketSizeLength2)
{
IPEndPoint ipEndPoint;
switch (protocol)
{
case NetworkProtocol.KCP:
ipEndPoint = NetworkHelper.ToIPEndPoint(address);
this.Service = new KService(ipEndPoint, this.OnAccept) { Parent = this };
break;
case NetworkProtocol.TCP:
ipEndPoint = NetworkHelper.ToIPEndPoint(address);
this.Service = new TService(packetSize, ipEndPoint, this.OnAccept) { Parent = this };
break;
case NetworkProtocol.WebSocket:
string[] prefixs = address.Split(';');
this.Service = new WService(prefixs, this.OnAccept) { Parent = this };
break;
}
}
这里公司用的是websocket
然后就是开启websocket监听 配置文件中InnerConfig地址 127.0.0.1:20002
websocket通讯服务端大概这样的
*******************websocket服务端****************************************
第一步:创建HttpListener类,并启动监听:
var listener = new HttpListener();
listener.Prefixes.Add("http://10.10.13.140:8080/");
listener.Start();
第二步:等待连接var context = listener.GetContext();
第三步:接收websocketvar wsContext = await context.AcceptWebSocketAsync(null);
var ws = wsContext.WebSocket;
Console.WriteLine("WebSocket connect");
第四步:开始异步接收数据//接收数据
var wsdata = await ws.ReceiveAsync(abuf, cancel);
Console.WriteLine(wsdata.Count);
byte[] bRec = new byte[wsdata.Count];
Array.Copy(buf, bRec, wsdata.Count);
Console.WriteLine(Encoding.Default.GetString(bRec));
第五步:释放资源//注意,使用完,记得释放,不然会有内存泄漏
ws.Dispose();
ET不一样的是在接收到websocket消息后 ,调用一个OnAccept方法 创建session组件 然后调用start方法
public void OnAccept(AChannel channel)
{
Session session = ComponentFactory.CreateWithParent<Session, AChannel>(this, channel);
this.sessions.Add(session.Id, session);
session.Start();
}
start方法调用WChannel的StartRecv() StartSend()方法
public async ETVoid StartRecv()
{
if (this.IsDisposed)
{
return;
}try
{
while (true)
{
#if SERVER
ValueWebSocketReceiveResult receiveResult;
#else
WebSocketReceiveResult receiveResult;
#endif
int receiveCount = 0;
do
{
#if SERVER
receiveResult = await this.webSocket.ReceiveAsync(
new Memory<byte>(this.recvStream.GetBuffer(), receiveCount, this.recvStream.Capacity - receiveCount),
cancellationTokenSource.Token);
#else
receiveResult = await this.webSocket.ReceiveAsync(
new ArraySegment<byte>(this.recvStream.GetBuffer(), receiveCount, this.recvStream.Capacity - receiveCount),
cancellationTokenSource.Token);
#endif
if (this.IsDisposed)
{
return;
}receiveCount += receiveResult.Count;
}
while (!receiveResult.EndOfMessage);if (receiveResult.MessageType == WebSocketMessageType.Close)
{
this.OnError(ErrorCode.ERR_WebsocketPeerReset);
return;
}if (receiveResult.Count > ushort.MaxValue)
{
await this.webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, $"message too big: {receiveResult.Count}",
cancellationTokenSource.Token);
this.OnError(ErrorCode.ERR_WebsocketMessageTooBig);
return;
}this.recvStream.SetLength(receiveResult.Count);
this.OnRead(this.recvStream);
}
}
catch (Exception e)
{
Log.Error(e);
this.OnError(ErrorCode.ERR_WebsocketRecvError);
}
}
从message字节流中接受消息 交由Session.OnRead方法处理 onread方法如下
包的前2个字节是opcode编码 后面的是消息体
private void Run(MemoryStream memoryStream)
{
memoryStream.Seek(Packet.MessageIndex, SeekOrigin.Begin);
ushort opcode = BitConverter.ToUInt16(memoryStream.GetBuffer(), Packet.OpcodeIndex);
#if !SERVER
if (OpcodeHelper.IsClientHotfixMessage(opcode))
{
this.GetComponent<SessionCallbackComponent>().MessageCallback.Invoke(this, opcode, memoryStream);
return;
}
#endif
object message;
try
{
OpcodeTypeComponent opcodeTypeComponent = this.Network.Entity.GetComponent<OpcodeTypeComponent>();
object instance = opcodeTypeComponent.GetInstance(opcode);
message = this.Network.MessagePacker.DeserializeFrom(instance, memoryStream);
if (OpcodeHelper.IsNeedDebugLogMessage(opcode))
{
Log.Msg(message);
}
}
catch (Exception e)
{
// 出现任何消息解析异常都要断开Session,防止客户端伪造消息
Log.Error($"opcode: {opcode} {this.Network.Count} {e}, ip: {this.RemoteAddress}");
this.Error = ErrorCode.ERR_PacketParserError;
this.Network.Remove(this.Id);
return;
}if (!(message is IResponse response))
{
this.Network.MessageDispatcher.Dispatch(this, opcode, message);
return;
}
Action<IResponse> action;
if (!this.requestCallback.TryGetValue(response.RpcId, out action))
{
throw new Exception($"not found rpc, response message: {StringHelper.MessageToStr(response)}");
}
this.requestCallback.Remove(response.RpcId);action(response);
}
如上代码所示, 解析出opcode后 从OpcodeTypeComponent组件中找到opcode对应的类型,把消息体反序列化成opcode对应的请求或者相应类
如果非IResponse消息的话 会把消息转给InnerMessageDispatcher类进行处理
public void Dispatch(Session session, ushort opcode, object message)
{
// 收到actor消息,放入actor队列
switch (message)
{
case IActorRequest iActorRequest:
{
Entity entity = (Entity)Game.EventSystem.Get(iActorRequest.ActorId);
if (entity == null)
{
Log.Warning($"not found actor: {message}");
ActorResponse response = new ActorResponse
{
Error = ErrorCode.ERR_NotFoundActor,
RpcId = iActorRequest.RpcId
};
session.Reply(response);
return;
}
MailBoxComponent mailBoxComponent = entity.GetComponent<MailBoxComponent>();
if (mailBoxComponent == null)
{
ActorResponse response = new ActorResponse
{
Error = ErrorCode.ERR_ActorNoMailBoxComponent,
RpcId = iActorRequest.RpcId
};
session.Reply(response);
Log.Error($"actor not add MailBoxComponent: {entity.GetType().Name} {message}");
return;
}
mailBoxComponent.Add(new ActorMessageInfo() { Session = session, Message = iActorRequest });
return;
}
case IActorMessage iactorMessage:
{
Entity entity = (Entity)Game.EventSystem.Get(iactorMessage.ActorId);
if (entity == null)
{
Log.Error($"not found actor: {message}");
return;
}
MailBoxComponent mailBoxComponent = entity.GetComponent<MailBoxComponent>();
if (mailBoxComponent == null)
{
Log.Error($"actor not add MailBoxComponent: {entity.GetType().Name} {message}");
return;
}
mailBoxComponent.Add(new ActorMessageInfo() { Session = session, Message = iactorMessage });
return;
}
default:
{
Game.Scene.GetComponent<MessageDispatcherComponent>().Handle(session, new MessageInfo(opcode, message));
break;
}
}
}
如上代码 消息类型有2种 IActorRequest IActorMessage
然后根据ActorId获取消息处理类
这个ActorId是啥时候加到eventsystem中的?
Game.EventSystem.Get(iActorRequest.ActorId);
然后交由MailBoxComponent.Add方法 把消息放到队列中
转载于:https://www.cnblogs.com/xtxtx/p/11273840.html
ET游戏框架整理笔记3: 常用内置组件功能相关推荐
- MySQL学习笔记_7_MySQL常用内置函数
MySQL常用内置函数 说明: 1)可以用在SELECT/UPDATE/DELETE中,及where,orderby,having中 2)在函数里将字段名作为参数,变量的值就是字段所对应的每一行的值. ...
- vue 路由的内置组件 router-view 详细介绍(有图有真相)
介绍 当你的地址符合我的路由要求的时候,会把路由的组件在 你的界面中呈现,而这个界面实在 App.vue里面 当你打开 App.vue就会在里面发现这两个组件 这两个是 vue 给你提供的两个 vue ...
- Python学习笔记:常用内建模块6 (urllib)
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python学习笔记:常用内建模块4:hmac
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Python学习笔记:常用内建模块3:struct
前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...
- Unity游戏框架学习笔记——03基于观察者模式的事件中心
Unity游戏框架学习笔记--03基于观察者模式的事件中心 基于观察者模式的事件中心 一如既往指路牌:https://www.bilibili.com/video/BV1C441117wU?p=5. ...
- 【学习笔记】16、常用内置函数(Built-in Functions)
常用内置函数(Built-in Functions) 可以参照中文版文档:Python3 内置函数 | 菜鸟教程 常用的内置函数可以做如下分类: 1.数据类型相关 type() – 取数据类型 int ...
- Python学习笔记(4)~Python基础练习之常用内置函数(1-10)
Python基础练习之常用内置函数(1-10) 1.abs() 作用 求数的绝对值或者复数的模 测试demo #!/usr/bin/python3 # abs: 求绝对值 or 复数的 ...
- 【Lin-CMS内容管理系统框架 v0.3.6】内置用户管理/权限管理/日志系统等常见功能
[Lin-CMS内容管理系统框架 v0.3.6]内置用户管理/权限管理/日志系统等常见功能 Lin-CMS 是林间有风团队经过大量项目实践所提炼出的一套内容管理系统框架. Lin-CMS 可以有效的帮 ...
最新文章
- python制作解压工具_使用python制作一个解压缩软件
- Python sort
- Spring MVC报异常:org.springframework.web.util.NestedServletException: Request processing failed
- XenApp/XenDesktop 7.11中对于视频、图片、文字的优化的说明
- 一次针对SAP系统的完美渗透测试
- Lync 2010迁移Lync 2013 PART6:迁移CMS
- hpg8服务器系列指示灯意思,HP Proliant GEN8服务器指示灯说明
- 删除Windows 系统快捷方式箭头 Delete Windows Shortcuct Arrows
- Python(21)_input用户交互
- 非root用户组启动sftp_如何在 Debian 10 中配置 Chroot 环境的 SFTP 服务 | Linux 中国
- 用于Activity管理和应用程序退出
- java异常处理代码详解_Java异常处理机制总结
- Linux笔记常规部分总结(待续)
- centos7 docker端口_docker相关知识
- Map按照key的ASCII码排序
- java塞班播放器_coreplayer移动播放器下载塞班版s60v5 v1.42_手机通用播放器
- 基于微信小程序的毕业设计题目(31)共享会议室预约小程序(含开题报告、任务书、中期报告、答辩PPT、论文模板)
- 教你十步实现内网穿透
- 天线理论知识5——微带天线
- “程序猿”的等级划分
热门文章
- php coder 乱码,MySQL 乱码 与 字符集
- elementui的css文件没有引入_为什么每个React组件都需要引入React?
- The driver is automatically registered via the SPI and manual loading of the
- switch 语句编译报错Constant expression required
- element UI表格使用cell-style改变单元格样式
- easyui181版本使用记录
- 加一度分享5大竞价经验,让流量更大更精准
- 让一让,神州泰岳要进化了
- [Head First设计模式]生活中学设计模式——组合模式
- [考试]20151008