概述:https://www.cnblogs.com/wang-jin-fu/p/10975660.html
本篇我們實現一種消息機制。為什么需要消息機制,很簡單,解耦合。
舉個例子,游戲里面當資源數量更新時(例如糧食+200),所有顯示該資源數量的界面都需要更新該資源的數量文本(例如訓練士兵、升級建築、治療、研發等等),這可能會涉及十幾種界面,而只有打開的界面需要更新。
那么當客戶端收到服務端的數量更新消息時,在邏輯類里一個個的判斷界面是否打開,如果界面打開則調用界面的更新方法顯然是很低效、耦合的。那么消息機制的實現方式是怎么樣的呢?
界面在打開時監聽一條事件RESOURCE_DATA_UPDATE,在關閉時移除該事件RESOURCE_DATA_UPDATE,邏輯類收到資源更新消息時會觸發這個事件,這樣每個監聽該事件的界面都可以收到資源更新的通知。
具體的實現是通過事件類型EventType 和c#委托Delegate實現的,EventType是自己定義的枚舉。關鍵代碼如下所示
private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();
public delegate void CallBack();
//添加一個事件(例如界面打開時) public static void AddListener(EventType eventType, CallBack callBack) { m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack; }
//移除一個事件(例如界面關閉時) public static void RemoveListener(EventType eventType, CallBack callBack) { m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); }
//觸發事件,邏輯類調用 public static void Broadcast(EventType eventType) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack callBack = d as CallBack; if (callBack != null) { callBack(); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } }
詳細代碼如下:
一、c#端
1.回調定義:原本打算使用params object[] param作為回調參數,這樣只需要定義一個回調。但是這種做法會導致頻繁的裝箱、拆箱操作,而且業務的代碼也不好寫。
裝箱:值類型轉引用類型,例如int裝object。對值類型進行裝箱會在堆中分配一個對象實例,並將該值復制到新的對象中。 最后會返回指向該內存的指針。我們應該盡量避免裝箱操作。
public delegate void CallBack(); public delegate void CallBack<T>(T arg); public delegate void CallBack<T, X>(T arg1, X arg2); public delegate void CallBack<T, X, Y>(T arg1, X arg2, Y arg3); public delegate void CallBack<T, X, Y, Z>(T arg1, X arg2, Y arg3, Z arg4); public delegate void CallBack<T, X, Y, Z, W>(T arg1, X arg2, Y arg3, Z arg4, W arg5); //public delegate void CallBack(params object[] param);
2.事件定義:
public enum EventType { ShowText, }
3.事件添加、移除、分發:最多支持五個參數的回調
public class EventCenter { private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>(); private static void OnListenerAdding(EventType eventType, Delegate callBack) { if (!m_EventTable.ContainsKey(eventType)) { m_EventTable.Add(eventType, null); } Delegate d = m_EventTable[eventType]; if (d != null && d.GetType() != callBack.GetType()) { throw new Exception(string.Format("嘗試為事件{0}添加不同類型的委托,當前事件所對應的委托是{1},要添加的委托類型為{2}", eventType, d.GetType(), callBack.GetType())); } } private static void OnListenerRemoving(EventType eventType, Delegate callBack) { if (m_EventTable.ContainsKey(eventType)) { Delegate d = m_EventTable[eventType]; if (d == null) { throw new Exception(string.Format("移除監聽錯誤:事件{0}沒有對應的委托", eventType)); } else if (d.GetType() != callBack.GetType()) { throw new Exception(string.Format("移除監聽錯誤:嘗試為事件{0}移除不同類型的委托,當前委托類型為{1},要移除的委托類型為{2}", eventType, d.GetType(), callBack.GetType())); } } else { throw new Exception(string.Format("移除監聽錯誤:沒有事件碼{0}", eventType)); } } private static void OnListenerRemoved(EventType eventType) { if (m_EventTable[eventType] == null) { m_EventTable.Remove(eventType); } } //no parameters public static void AddListener(EventType eventType, CallBack callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack; } //Single parameters public static void AddListener<T>(EventType eventType, CallBack<T> callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] + callBack; } //two parameters public static void AddListener<T, X>(EventType eventType, CallBack<T, X> callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] + callBack; } //three parameters public static void AddListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] + callBack; } //four parameters public static void AddListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] + callBack; } //five parameters public static void AddListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack) { OnListenerAdding(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] + callBack; } //no parameters public static void RemoveListener(EventType eventType, CallBack callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //single parameters public static void RemoveListener<T>(EventType eventType, CallBack<T> callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //two parameters public static void RemoveListener<T, X>(EventType eventType, CallBack<T, X> callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X>)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //three parameters public static void RemoveListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //four parameters public static void RemoveListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //five parameters public static void RemoveListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack) { OnListenerRemoving(eventType, callBack); m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] - callBack; OnListenerRemoved(eventType); } //no parameters public static void Broadcast(EventType eventType) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack callBack = d as CallBack; if (callBack != null) { callBack(); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } //single parameters public static void Broadcast<T>(EventType eventType, T arg) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack<T> callBack = d as CallBack<T>; if (callBack != null) { callBack(arg); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } //two parameters public static void Broadcast<T, X>(EventType eventType, T arg1, X arg2) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack<T, X> callBack = d as CallBack<T, X>; if (callBack != null) { callBack(arg1, arg2); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } //three parameters public static void Broadcast<T, X, Y>(EventType eventType, T arg1, X arg2, Y arg3) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack<T, X, Y> callBack = d as CallBack<T, X, Y>; if (callBack != null) { callBack(arg1, arg2, arg3); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } //four parameters public static void Broadcast<T, X, Y, Z>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack<T, X, Y, Z> callBack = d as CallBack<T, X, Y, Z>; if (callBack != null) { callBack(arg1, arg2, arg3, arg4); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } //five parameters public static void Broadcast<T, X, Y, Z, W>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4, W arg5) { Delegate d; if (m_EventTable.TryGetValue(eventType, out d)) { CallBack<T, X, Y, Z, W> callBack = d as CallBack<T, X, Y, Z, W>; if (callBack != null) { callBack(arg1, arg2, arg3, arg4, arg5); } else { throw new Exception(string.Format("廣播事件錯誤:事件{0}對應委托具有不同的類型", eventType)); } } } }
二、lua端,lua對調並不需要定義。
1.事件定義:
local MESSAGE = 0 local function GetID() MESSAGE = MESSAGE + 1 return MESSAGE end --消息table GMsgDef = { SOCKET_TCP_DATA = GetID(), }
2.事件處理:
local GameMsg={} local Messagelisteners = {} local GMsgDef = GMsgDef local _msgPool = ObjectPool.new(function() return {} end, "GameMsgPool", false) local function GetMsg(msgName) local msg = GMsgDef[msgName] if msg == nil then printErrorF("未定義MSG_{%s}", msgName) end return msg end -- msg 為GMsgDef.XXX -- target 因為調用類的成員需要target,如果backFunc不是類成員 則target傳nil;target也用來在實例Dispos的時候統一移除監聽,視情況傳 -- backFunc 回調函數 function GameMsg.AddMessage(msgName, target, backFunc) if not msgName then return end local msg = GetMsg(msgName) --操作 local lstListeners = Messagelisteners[msg] if lstListeners == nil then lstListeners = {} Messagelisteners[msg] = lstListeners end if GameMsg.HasMessageAlready(msg, target, backFunc) then printWarning("GameMsg.AddMessage duplicate, ", msgName) return end local msgObj = _msgPool:Pop() msgObj.target = target msgObj.func = backFunc table.insert(lstListeners, msgObj) end -- 傳入監聽消息列表 function GameMsg.AddMessageList(msglist, target, backFunc) if not msglist then return end for _,v in ipairs(msglist) do GameMsg.AddMessage(v, target, backFunc) end end function GameMsg.SendMessage(msgName, ...) -- target local msg = GetMsg(msgName) local msgs = Messagelisteners[msg] if msgs == nil then return end for i = #msgs, 1, - 1 do local l = msgs[i] if l then local func = l["func"] local target = l["target"] if func then if target then func(target, ...) else func(...) end end end end end -- 對象和方法都有相應映射的話就返回 function GameMsg.HasMessageAlready(msg, target, func) local msgs = Messagelisteners[msg] if not msgs then return false end for k, v in ipairs(msgs) do if v["func"] == func and v.target==target then return true end end return false end -- 移除某個消息監聽 function GameMsg.RemoveMessage(msgName, target, func) local msg = GetMsg(msgName) local msgs = Messagelisteners[msg] if not msgs then return end for i=#msgs,1,-1 do local item = msgs[i] if item["func"] == func and item.target==target then _msgPool:Push(msgs[i]) table.remove(msgs, i) end end return false end function GameMsg.RemoveMessageByTarget(target) for k,v in pairs(Messagelisteners) do for i=#v,1,-1 do if v[i]["target"] == target then _msgPool:Push(v[i]) table.remove(v, i) end end end end function GameMsg.RemoveMessageFromTargetByName(target, msgName) --操作 local msg = GetMsg(msgName) local msgs = Messagelisteners[msg] if msgs then for i=#msgs,1,-1 do if msgs[i]["target"] == target then _msgPool:Push(msgs[i]) table.remove(msgs, i) end end end end function GameMsg.RemoveAllMessage() Messagelisteners = {} end return GameMsg