Unity 游戏框架搭建 2019 (四十八) MonoBehaviourSimplify 中的消息策略完善

在上一篇,笔者说,MonoBehaviourSimplify 中的消息策略还有一些小问题。我们在这篇试着解决一下。

先贴出来代码:

using System;
using System.Collections.Generic;

namespace QFramework
{
    public abstract partial class MonoBehaviourSimplify
    {
        Dictionary> mMsgRegisterRecorder = new Dictionary>();

        protected void RegisterMsg(string msgName, Action onMsgReceived)
        {
            MsgDispatcher.Register(msgName, onMsgReceived);
            mMsgRegisterRecorder.Add(msgName, onMsgReceived);
        }

        
        private void OnDestroy()
        {
            OnBeforeDestroy();
            
            foreach (var keyValuePair in mMsgRegisterRecorder)
            {
                MsgDispatcher.UnRegister(keyValuePair.Key,keyValuePair.Value);                
            }
            
            mMsgRegisterRecorder.Clear();
        }

        protected abstract void OnBeforeDestroy();
    }

    public class B : MonoBehaviourSimplify
    {
        private void Awake()
        {
            RegisterMsg("Do", DoSomething);
            RegisterMsg("DO1", _ => { });
            RegisterMsg("DO2", _ => { });
            RegisterMsg("DO3", _ => { });
        }

        void DoSomething(object data)
        {
            // do something
        }

        protected override void OnBeforeDestroy()
        {
            
        }
    }
}
 
  

我们是使用字典进行注册消息的记录的,使用字典就要保证字典中的 key 是唯一的。而我们很可能在一个脚本中对一个关键字注册多次,这样用字典这个数据结构就显得不合理了。

相比字典,List 更合适,因为我们有有可能有重复的内容,而字典更适合做一些查询工作,但是 List 并不支持键值对,怎么办呢?

我们只好创建一个结构来存储我们的消息名和对应的委托,这个结构是一个类叫做 MsgRecord

消息策略部分的代码如下:

    public abstract partial class MonoBehaviourSimplify
    {
        List mMsgRecorder = new List();

        private class MsgRecord
        {
            public string Name;

            public Action OnMsgReceived;
        }

        protected void RegisterMsg(string msgName, Action onMsgReceived)
        {
            MsgDispatcher.Register(msgName, onMsgReceived);
            mMsgRecorder.Add(new MsgRecord
            {
                Name = msgName,
                OnMsgReceived = onMsgReceived
            });
        }
        
        private void OnDestroy()
        {
            OnBeforeDestroy();
            
            foreach (var msgRecord in mMsgRecorder)
            {
                MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);                
            }
            
            mMsgRecorder.Clear();
        }

        protected abstract void OnBeforeDestroy();
    }
 
  

代码比较简单。

而我们的示例代码,如下,增加了一行重复注册的代码。

    public class B : MonoBehaviourSimplify
    {
        private void Awake()
        {
            RegisterMsg("Do", DoSomething);
            RegisterMsg("Do", DoSomething);
            RegisterMsg("DO1", _ => { });
            RegisterMsg("DO2", _ => { });
            RegisterMsg("DO3", _ => { });
        }

        void DoSomething(object data)
        {
            // do something
        }

        protected override void OnBeforeDestroy()
        {
            
        }
    }

而我们的 MonoBehaviourSimplify 内部实现发生了天翻地覆的变化,也没有对我们的示例代码产生一点影响,这叫封装。

那么到这里,我们的消息策略还有问题吗?

还有的,问题在创建 MsgRecord 的部分。
如下:

mMsgRecorder.Add(new MsgRecord
{
	Name = msgName,
	OnMsgReceived = onMsgReceived
});

我们每次注册消息,都要 new 一个 MsgRecord 对象出来,而我们在注销的时候,对这个对象是什么都没有做的,注销的代码如下:

foreach (var msgRecord in mMsgRecorder)
{
	MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);                
}

这样会造成一个性能问题,这个性能问题主要是有 new 时候寻址造成的,具体原因自行搜索,当然在本专栏的后边还是会介绍的。我们要做的,就是减少 new 的发生次数,要想减少,就得让我们的 MsgRecord 能够回收利用。

如何回收利用呢,答案是维护一个容器,比如 List 或者 Queue、Stack 等,也就是传说中的对象池。由于我们的 MsgRecord 的作用仅仅是作为一个存储结构而已,而存储的顺序也不是很重要,所以我们就用做简单的 Stack 结构,也就是栈,来作为 MsgRecord 对象池的容器。

其实现如下:

private class MsgRecord
{
	static Stack mMsgRecordPool = new Stack();

	public static MsgRecord Allocate()
	{
		if (mMsgRecordPool.Count > 0)
		{
			return mMsgRecordPool.Pop();
		}

		return new MsgRecord();
	}

	public void Recycle()
	{
		Name = null;
		OnMsgReceived = null;
                
		mMsgRecordPool.Push(this);
	}

	public string Name;

	public Action OnMsgReceived;
}
 
  

由于这个对象池只给 MsgRecord 用,所以就在 MsgRecord 内部实现了。
Allocate 是申请,也就是获取对象。Recycle 就是回收,当不用的时候调用一下就好了。

原理很简单。而 mMsgRecordPool 之所以设置成了 private 访问权限,是因为,不希望被外部访问到。对于一个类的设计来讲,MsgRecord 是一个非常合格的类了。

应用到我们的消息策略的代码如下:

protected void RegisterMsg(string msgName, Action onMsgReceived)
{
	MsgDispatcher.Register(msgName, onMsgReceived);
            
	// 
	var msgRecord = MsgRecord.Allocate();

	msgRecord.Name = msgName;
	msgRecord.OnMsgReceived = onMsgReceived;
            
	mMsgRecorder.Add(msgRecord);
}
        
private void OnDestroy()
{
	OnBeforeDestroy();
            
	foreach (var msgRecord in mMsgRecorder)
	{
		MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);  
		//
		msgRecord.Recycle();
	}
            
	mMsgRecorder.Clear();
}
 
  

我们发现,在申请对象部分可以简化成如下:

// var msgRecord = MsgRecord.Allocate();
//            
// msgRecord.Name = msgName;
// msgRecord.OnMsgReceived = onMsgReceived;
//            
// mMsgRecorder.Add(msgRecord);

mMsgRecorder.Add(MsgRecord.Allocate(msgName, onMsgReceived));

只需要向 MsgRecord.Allocate 增加参数,代码如下:

public static MsgRecord Allocate(string msgName,Action onMsgReceived)
{
	MsgRecord retMsgRecord = null;
                
	retMsgRecord = mMsgRecordPool.Count > 0 ? mMsgRecordPool.Pop() : new MsgRecord();

	retMsgRecord.Name = msgName;
	retMsgRecord.OnMsgReceived = onMsgReceived;

	return retMsgRecord;
}
 
  

代码不难,那么到这里,我们的完整的第十三个示例就写完了。

完整示例代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework
{
    public abstract partial class MonoBehaviourSimplify
    {
        List mMsgRecorder = new List();

        private class MsgRecord
        {
            private static readonly Stack mMsgRecordPool = new Stack();

            public static MsgRecord Allocate(string msgName,Action onMsgReceived)
            {
                MsgRecord retMsgRecord = null;
                
                retMsgRecord = mMsgRecordPool.Count > 0 ? mMsgRecordPool.Pop() : new MsgRecord();

                retMsgRecord.Name = msgName;
                retMsgRecord.OnMsgReceived = onMsgReceived;

                return retMsgRecord;
            }

            public void Recycle()
            {
                Name = null;
                OnMsgReceived = null;
                
                mMsgRecordPool.Push(this);
            }

            public string Name;

            public Action OnMsgReceived;
        }

        protected void RegisterMsg(string msgName, Action onMsgReceived)
        {
            MsgDispatcher.Register(msgName, onMsgReceived);
            
            mMsgRecorder.Add(MsgRecord.Allocate(msgName, onMsgReceived));
        }
        
        private void OnDestroy()
        {
            OnBeforeDestroy();
            
            foreach (var msgRecord in mMsgRecorder)
            {
                MsgDispatcher.UnRegister(msgRecord.Name,msgRecord.OnMsgReceived);  
                msgRecord.Recycle();
            }
            
            mMsgRecorder.Clear();
        }

        protected abstract void OnBeforeDestroy();
    }
    
    
    public class MsgDistapcherInMonoBehaviourSimplify : MonoBehaviourSimplify
    {
#if UNITY_EDITOR
        [UnityEditor.MenuItem("QFramework/13.消息机制集成到 MonoBehaviourSimplify", false, 14)]
        private static void MenuClicked()
        {
            UnityEditor.EditorApplication.isPlaying = true;

            new GameObject("MsgReceiverObj")
                .AddComponent();
        }
#endif
        private void Awake()
        {
            RegisterMsg("Do", DoSomething);
            RegisterMsg("Do", DoSomething);
            RegisterMsg("DO1", _ => { });
            RegisterMsg("DO2", _ => { });
            RegisterMsg("DO3", _ => { });
        }

        private IEnumerator Start()
        {
            MsgDispatcher.Send("Do","hello");
            
            yield return new WaitForSeconds(1.0f);
            
            MsgDispatcher.Send("Do","hello1");
        }

        void DoSomething(object data)
        {
            // do something
            Debug.LogFormat("Received Do msg:{0}",data);
        }

        protected override void OnBeforeDestroy()
        {
            
        }
    }
}
 
  

运行结果如下图:
Unity 游戏框架搭建 2019 (四十八) MonoBehaviourSimplify 中的消息策略完善_第1张图片

菜单栏如下图:
Unity 游戏框架搭建 2019 (四十八) MonoBehaviourSimplify 中的消息策略完善_第2张图片

目录如下图:
Unity 游戏框架搭建 2019 (四十八) MonoBehaviourSimplify 中的消息策略完善_第3张图片

到这里我们可以进行一次导出了。

今天的内容就这些,我们下一篇在见,拜拜~

转载请注明地址:凉鞋的笔记:liangxiegame.com

更多内容

  • QFramework 地址:https://github.com/liangxiegame/QFramework

  • QQ 交流群:623597263

  • Unity 进阶小班

    • 主要训练内容:
      • 框架搭建训练(第一年)
      • 跟着案例学 Shader(第一年)
      • 副业的孵化(第二年、第三年)
    • 权益、授课形式等具体详情请查看《小班产品手册》:https://liangxiegame.com/master/intro
  • 关注公众号:liangxiegame 获取第一时间更新通知及更多的免费内容。

你可能感兴趣的:(Unity,游戏框架搭建,Unity,Unity游戏框架搭,unity,游戏框架,c#)