80. 2023-10-01周总结

  1. EventManager Bug
  2. YooAsset卸载附加场景导致主场景材质丢失

1. EventManager Bug

EventManager是我们的消息管理类,提供注册消息回调和发送消息接口。

最近写的时候发现了一个bug,注册消息回调之后,发送消失的时候有些回调没有进,定位了好久才发现这个消除处理循环有问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <summary>
/// 同步发送消息
/// </summary>
/// <typeparam name="T"></typeparam>
public void SendMessage<T>(T message) where T : IEventMessage
{
Type t = typeof(T);
if (_eventListMap.TryGetValue(t, out var list))
{
for (int i = 0; i < list.Count; i++)
{
list[i].Invoke(message);
}
}
}

这一看是没有问题的,但是如果在回调里面调用了RemoveListener函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// <summary>
/// 删除监听事件
/// </summary>
/// <param name="type">监听消息类型</param>
/// <param name="callback">回调函数</param>
public void RemoveListener(Type type, Action<IEventMessage> callback)
{
if (_eventListMap.TryGetValue(type, out var list))
{
list.Remove(callback);

if (list.Count == 0)
{
_eventListMap.Remove(type);
}
}
}

相当于在循环List中给List删除了元素(这时候如果用foreach循环,会抛出异常,而我List比较喜欢用小标循环,所以规避了这个异常),在调用自己的过程中删除自己,后面的元素向前移动,这样正好导致紧跟后面的这个元素没被回调了。

修复方式也很简单,回调的时候Copy一次List,避免回调过程中List被操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 同步发送消息
/// </summary>
/// <typeparam name="T"></typeparam>
public void SendMessage<T>(T message) where T : IEventMessage
{
Type t = typeof(T);
if (_eventListMap.TryGetValue(t, out var list))
{
var cloneList = list.Clone();
for (int i = 0; i < cloneList.Count; i++)
{
cloneList[i].Invoke(message);
}
}
}

Clone我们封装的方法,里面简单的创建一个List,把所有元素Copy过去。

2. YooAsset卸载附加场景导致主场景材质丢失

为了方便资源管理,我们会对游戏分模块,比如主场景模块,推图模块,战斗模块,模块内部不会主动调用UnloadUnusedAssets,并且模块内部所加载的资源加载成功之后会主动释放资源句柄。

同时在从一个模块进入到另外一个模块的时候,会读loading,并且会调用UnloadUnusedAssets卸载资源,这样就能卸载干净上个模块的所有资源了。

这样的好处就是游戏层没有特殊需求是不需要知道资源什么时候释放的,只需要在用到的地方进行加载即可,方便上层进行开发。

但最近发现一个问题YooAsset在卸载附加场景的时候主动调用了一次UnloadUnusedAssets,导致我们之前加载的东西被卸载了,这应该是他的bug,我看最新版本已经不会主动调用UnloadUnusedAssets了。因为最新版本改动比较大,所以我们下载了当前使用版本的源码,手动把这个调用删除了。

其他

开开心心带着女儿出去玩了两天,结果30号晚上开始发烧了,预感假期要泡汤了。。。