新的一年,新的開始,祝願大家新春快樂!轉載請標明出處:http://www.cnblogs.com/zblade/
今天,開始寫新的一年的博客了,我的博客更新的比較隨意,也比較緩慢,哈哈。前段時間一直在看一些D3D11的博客,自己也在摸索各個API的使用,不過今天要寫的是一篇游戲中的事件系統的設計,具體的事件系統的設計模式(觀察者模式),我就不再贅述了,網上有很多相關資料,可以自行查閱學習了解一下相關的原理,比較簡單易懂。
在游戲開發中,我們經常會和服務器進行信息的交互,比如玩家點擊了商城里面的領取獎勵的按鈕,此時客戶端會發送一個領取的協議給服務器,服務器校驗通過后,會下發給客戶端,客戶端在收到消息后,會重新刷新一下相關的數據,那么這些數據的刷新怎么表現在UI上面呢,就需要我們通過事件系統來通知UI層進行相關的刷新操作了。
最常見的事件系統,就是我上面說到的,將數據層和UI層進行分離,通過事件系統的調用來實現耦合的拆分。這在游戲中有較多的應用,那么,我們今天先說一下這種事件系統的設計模式吧。
一、常見事件系統的設計
要設計一個事件系統,一般會暴露最常見的三個接口:Add/Remove/Trigger,分別負責事件的添加,移除和觸發。依然用Lua來實現這樣的一個接口,我們可以用幾個table來實現一個基本的事件系統的三個接口:
local events = {} local eventHandleId = 0
local eventHandles = {} --設置兩個內部函數操作增加和刪除
local function AddListener(name, listener) local listeners = events[name] if not listeners then listeners = {} events[name] = listeners end
local handleId = listeners[listener] if not handleId then handleId = eventHandleId + 1 eventHandleId = handleId --此處插入,用function來用作key值
listeners[listener] = handleId eventHandles[handleId] = { name, listener } end
return handleId end
local function RemoveListener(name, listener, handles) local listeners = events[name] if listeners then
if not listener then
--該name下對應的所有都清空
for _, handleId in pairs(listeners) do eventHandles[handleId] = nil
if handles then handles[handleId] = nil
end
end events[name] = nil
else
--特定對應的某個listner刪除
local handleId = listeners[listener] if handleId then listeners[listener] = nil eventHandles[handleId] = nil
if handles then handles[handleId] = nil
end
end
end
end
end
--指定刪除某個handleId對應的事件
local function RemoveHandle(handleId) local entry = eventHandles[handleId] if entry then RemoveListener(entry[1], entry[2]) end
end
--
local EventManager = {} --增加
function EventManager.Add(name, listener) AddListener(name, listener) end
--刪除
function EventManager.Remove(name, listener) RemoveListener(name, listener) end
--清空
function EventManager.Clear() events = {} eventHandles = {} end
--觸發
function EventManager.Dispatch(name, ...) local listeners = events[name] if listeners then
for listener, _ in pairs(listeners) do
--基於key值(實質為函數)來執行觸發操作
listener(name, ...) end
end
end
巧用lua中的table,我們可以實現一個最基本的事件系統,具體的原理可以參看lua代碼來理解,不是很難。這是最基本的事件系統設計,在此基礎上,我們可以進一步的優化我們的事件系統。我們在進行事件系統的注冊、刪除和觸發的時候,並沒有考慮到並發性。比如同時有多個消息過來,要求我們對同一個事件進行處理,這時候事件系統就需要考慮並發性的設計了。
二、處理並發性的事件系統設計
對於並發性的處理,許多常見的思路都給出了不同的處理辦法,比如加鎖就是一個比較好的處理辦法。在執行觸發的操作的時候,這是就對添加的函數進行滯后處理,這樣可以避免在執行觸發操作的時候,又塞入一個新的監聽,造成觸發隱藏問題。可以這樣處理:
local events = {} local eventHandleId = 0 local eventHandles = {} local function AddListener(name, listener) local listeners = events[name] if not listeners then --listeners不再是一個簡單的table,通過多個標識符來標識 listeners = { insert = {}, dirty = false, executing = false, destroyed = false } events[name] = listeners end local handleId = listeners[listener] or listeners.insert[listener] if not handleId then handleId = eventHandleId + 1 eventHandleId = handleId --如果在塞入的時候,該listeners正在被觸發,則不執行立即塞入的操作,等下一個觸發到來的時候執行 if listeners.executing then listeners.insert[listener] = handleId listeners.dirty = true else listeners[listener] = handleId end eventHandles[handleId] = { name, listener } end return handleId end local function RemoveListener(name, listener, handles) local listeners = events[name] if listeners then if not listener then for _, handleId in pairs(listeners) do eventHandles[handleId] = nil if handles then handles[handleId] = nil end end --標記其已經destroyed listeners.destroyed = true events[name] = nil else local handleId = listeners[listener] or listeners.insert[listener] if handleId then listeners[listener] = nil listeners.insert[listener] = nil eventHandles[handleId] = nil if handles then handles[handleId] = nil end end end end end local function RemoveHandle(handleId) local entry = eventHandles[handleId] if entry then RemoveListener(entry[1], entry[2]) end end local EventManager = {} function EventManager.Add(name, listener) AddListener(name, listener) end function EventManager.Remove(name, listener) RemoveListener(name, listener) end function EventManager.Clear() events = {} eventHandles = {} end function EventManager.Dispatch(name, ...) local listeners = events[name] if listeners then listeners.executing = true for listener, _ in pairs(listeners) do if type(listener) == "function" then listener(name, ...) end --可能在執行listener的過程中回調執行了remove,所以需要檢測一次是否退出 if listeners.destroyed then return end end --觸發完后,再執行緩存的塞入檢測 if listeners.dirty then for listener, handleId in pairs(listeners.insert) do listeners[listener] = handleId listeners.insert[listener] = nil end listeners.dirty = false end listeners.executing = false end end
如果不用lua,改用c#來實現,則需要巧妙的運用c#中的鏈表來實現對應的操作,這兒我也給出一份c#鏈表的相關實現吧:)
using System; using System.Collections.Generic; public class EventListener { public string name; public Delegate action; } public static class EventManger { private static Dictionary<string, List<EventListener>> listenList = new Dictionary<string, List<EventListener>>(); private static Dictionary<string, int> listenerStatus = new Dictionary<string, int>(); private static Dictionary<string, List<EventListener>> addList = new Dictionary<string, List<EventListener>>(); private static Dictionary<string, List<EventListener>> removeList = new Dictionary<string, List<EventListener>>(); //查找監聽 public static EventListener GetListener(string name, Delegate action) { if (action == null) return null; List<EventListener> listEvent = null; if (!listenList.TryGetValue(name, out listEvent) || listEvent.Count < 1) return null; var ls = listEvent.Find(l => l.action.Method == action.Method); return ls; } //添加事件監聽 public static EventListener AddEventListener(string name, Delegate action) { if (action == null) return null; //判斷是否已經有 var listener = GetListener(name, action); if (listener != null) return listener; //new 可以用資源池代替 listener = new EventListener(); listener.name = name; listener.action = action; //第一次創建,則建立對應的dic if(!listenList.ContainsKey(name)) { listenList.Add(name, new List<EventListener>()); addList.Add(name, new List<EventListener>()); removeList.Add(name, new List<EventListener>()); listenerStatus.Add(name, 0); } //處於事件觸發,則滯后處理 if (listenerStatus[name] > 0) addList[name].Add(listener); else listenList[name].Add(listener); return listener; } //刪除事件監聽 public static void RemoveListener(string name, EventListener listener) { List<EventListener> deleteList = null; if(listenList.TryGetValue(name, out deleteList)) { //首先刪除addList中的監聽 addList[name].Remove(listener); if(deleteList.Contains(listener)) { //如果正在觸發,則滯后 if (listenerStatus[name] > 0) removeList[name].Add(listener); else { deleteList.Remove(listener); //歸還到資源池去........ } } } } //事件觸發 public static void DispatchEvent(string name) { //............... List<EventListener> ls = null; if(listenList.TryGetValue(name, out ls)) { //標記正在觸發 listenerStatus[name]++; // foreach(var listener in ls) { listener.action.DynamicInvoke(null); } var count = listenerStatus[name] - 1; listenerStatus[name] = count; //此時執行滯后的增刪操作 if(count < 1) { var list = addList[name]; ls.AddRange(list); list.Clear(); list = removeList[name]; foreach (var listener in list) ls.Remove(listener); list.Clear(); } //....... } } }
用listenerStatus來標志是否處於事件觸發的狀態, 可以較為巧妙的避開同時對鏈表的操作,當然實際的應用在還會添加一些限定條件,判定條件,避免某些不符合常規的操作帶來的風險,具體需要結合項目來進行相關的實現即可。好了,今天的文章就寫到這兒,后續再繼續更新 :D
