مجمع الحدث للحدث Unity3d

فكرة كتابة مجمع الأحداث الموسعة الخاصة بك لـ Unity3d طال انتظارها. بعد قراءة العديد من المقالات حول هذا الموضوع ، أدركت أنه لا يوجد ما يكفي من "الصحيح" (داخل Unity3d) والمجمع الضروري بالنسبة لي ، يتم تجريد جميع الحلول من عدم وجود الوظائف اللازمة.

الوظيفة المطلوبة:


  1. يمكن لأي فئة الاشتراك في أي حدث (غالبًا ما يقوم المجمعون في وحدة بعمل مشتركين محددين في Gameobject)
  2. يجب استبعاد إمكانية الاشتراك المزدوج في مثيل معين لحدث معين (في الأدوات القياسية تحتاج إلى اتباع هذا بنفسك)
  3. يجب أن تكون هناك وظيفة لإلغاء الاشتراك اليدوي وتلقائي ، في حالة حذف مثيل / تعطيل monobekh (أريد الاشتراك وعدم أخذ حمام بخار قام المشترك بإلقاء الحافر فجأة)
  4. يجب أن تكون الأحداث قادرة على نقل البيانات / الروابط بأي تعقيد (أريد الاشتراك في سطر واحد والحصول على مجموعة كاملة من البيانات دون مشاكل)

حيث لتطبيقه


  1. هذا مثالي لواجهة المستخدم ، عندما تكون هناك حاجة لإعادة توجيه البيانات من أي كائن دون أي اتصال.
  2. رسائل حول تغييرات البيانات ، بعض التماثلية من رمز رد الفعل.
  3. لحقن التبعية
  4. الاسترجاعات العالمية

نقاط ضعف


  1. بسبب الشيكات على المشتركين القتلى ويأخذ (سأكشف لاحقا) الرمز أبطأ من الحلول المماثلة
  2. يتم استخدام الفئة / البنية كجوهر للحدث ، حتى لا يتم تخصيص ذاكرة + المشكلة الكبرى ، لا ينصح بإحداث رسائل غير مرغوب فيها في التحديث)

الأيديولوجية العامة


الأيديولوجية العامة هي أن الحدث بالنسبة لنا هو حزمة بيانات محددة وذات صلة. دعنا نقول أننا ضغطنا على زر على واجهة / عصا التحكم. ونريد إرسال حدث مع وجود علامات على الضغط على زر معين لمزيد من المعالجة. نتيجة معالجة النقرات هي التغييرات المرئية على الواجهة ونوع من الإجراء في المنطق. وفقًا لذلك ، قد يكون هناك معالجة / اشتراك في مكانين مختلفين.

كيف تبدو مجموعة نص / حزمة الحدث في حالتي:

مثال على هيئة الحدث
public struct ClickOnButtonEvent   {     public int ButtonID; //     enum    } 


كيف يبدو اشتراك الحدث:

 public static void AddListener<T>(object listener, Action<T> action) 

للاشتراك ، نحتاج إلى تحديد:
كائن يمثل مشتركًا (عادةً ما يكون هو الفئة نفسها التي الاشتراك فيها ، ولكن هذا ليس ضروريًا ، يمكن للمشترك تحديد أحد مثيلات الفئة من حقول الفئة.
النوع / الحدث الذي نشترك فيه. هذا هو الجوهر الرئيسي لهذا المجمع ، بالنسبة لنا نوع معين من الفصل هو حدث نستمع إليه ونعالجه.
الاشتراك الأفضل في Awake و OnEnable ؛

مثال

 public class Example : MonoBehaviour { private void Awake() { EventAggregator.AddListener<ClickOnButtonEvent>(this, ClickButtonListener); } private void ClickButtonListener(ClickOnButtonEvent obj) { Debug.Log("  " + obj.ButtonID); } } 

لتوضيح رقاقة ما ، والنظر في حالة أكثر تعقيدا


لدينا رموز الشخصيات التي:
  1. معرفة أي شخصية هم المرتبطة بها.
  2. تعكس كمية مانا ، حصان ، إكسب ، والحالات (الصاعقة ، العمى ، الخوف ، الجنون)

وهنا يمكنك القيام بالعديد من الأحداث

لتغيير المؤشرات:

 public struct CharacterStateChanges { public Character Character; public float Hp; public float Mp; public float Xp; } 

لتغيير الحالات السلبية:

 public struct CharacterNegativeStatusEvent { public Character Character; public Statuses Statuses; //enum  } 

لماذا في كلتا الحالتين هل نجتاز فئة الشخصية؟ هنا هو المشترك الحدث ومعالجها:

 private void Awake() { EventAggregator.AddListener<CharacterNegativeStatusEvent> (this, CharacterNegativeStatusListener); } private void CharacterNegativeStatusListener(CharacterNegativeStatusEvent obj) { if (obj.Character != _character) return; _currentStatus = obj.Statuses; } 

هذه هي العلامة التي نعالج بها الحدث ونفهم ما نحتاجه بالضبط.
لماذا لا تفترض أنك اشتركت مباشرة في فئة الشخصيات؟ والبريد المزعج لهم؟
سيكون من الصعب الظهور لأول مرة ، فمن الأفضل لمجموعة من الفصول / الأحداث إنشاء حدث منفصل خاص بهم.

لماذا ، مرة أخرى ، داخل الحدث ، فقط لا تضع شخصية وتأخذ كل شيء منه؟
بالمناسبة ، يكون ذلك ممكنًا ، لكن غالبًا ما توجد في الفصول قيود على الرؤية ، وقد لا تكون البيانات اللازمة للحدث مرئية من الخارج.

إذا كان الفصل ثقيلًا جدًا لاستخدامه كعلامة؟
في الواقع ، في معظم الحالات ، ليست هناك حاجة إلى علامة ؛ مجموعة من الطبقات المحدثة نادرة إلى حد ما. عادةً ما يحتاج كيان محدد إلى حدث - نموذج تحكم / عرض ، يعرض عادةً حالة الحرف الأول. وهكذا يوجد دائمًا حل عادي - معرفات من أنواع مختلفة (من inam ، إلى التجزئة المعقدة ، إلخ).

ما هو تحت غطاء محرك السيارة وكيف يعمل؟


كود مجمع مباشرة
 namespace GlobalEventAggregator public delegate void EventHandler<T>(T e); { public class EventContainer<T> : IDebugable { private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>(); private const string Error = "null"; public bool HasDuplicates(object listener) { return _activeListenersOfThisType.Keys.Any(k => k.Target == listener); } public void AddToEvent(object listener, EventHandler<T> action) { var newAction = new WeakReference(listener); _activeListenersOfThisType.Add(newAction, action); _eventKeeper += _activeListenersOfThisType[newAction]; } public void RemoveFromEvent(object listener) { var currentEvent = _activeListenersOfThisType.Keys.FirstOrDefault(k => k.Target == listener); if (currentEvent != null) { _eventKeeper -= _activeListenersOfThisType[currentEvent]; _activeListenersOfThisType.Remove(currentEvent); } } public EventContainer(object listener, EventHandler<T> action) { _eventKeeper += action; _activeListenersOfThisType.Add(new WeakReference(listener), action); } public void Invoke(T t) { if (_activeListenersOfThisType.Keys.Any(k => k.Target.ToString() == Error)) { var failObjList = _activeListenersOfThisType.Keys.Where(k => k.Target.ToString() == Error).ToList(); foreach (var fail in failObjList) { _eventKeeper -= _activeListenersOfThisType[fail]; _activeListenersOfThisType.Remove(fail); } } if (_eventKeeper != null) _eventKeeper(t); return; } public string DebugInfo() { string info = string.Empty; foreach (var c in _activeListenersOfThisType.Keys) { info += c.Target.ToString() + "\n"; } return info; } } public static class EventAggregator { private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>(); static EventAggregator() { SceneManager.sceneUnloaded += ClearGlobalListeners; } private static void ClearGlobalListeners(Scene scene) { GlobalListeners.Clear(); } public static void AddListener<T>(object listener, Action<T> action) { var key = typeof(T); EventHandler<T> handler = new EventHandler<T>(action); if (GlobalListeners.ContainsKey(key)) { var lr = (EventContainer<T>)GlobalListeners[key]; if (lr.HasDuplicates(listener)) return; lr.AddToEvent(listener, handler); return; } GlobalListeners.Add(key, new EventContainer<T>(listener, handler)); } public static void Invoke<T>(T data) { var key = typeof(T); if (!GlobalListeners.ContainsKey(key)) return; var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.Invoke(data); } public static void RemoveListener<T>(object listener) { var key = typeof(T); if (GlobalListeners.ContainsKey(key)) { var eventContainer = (EventContainer<T>)GlobalListeners[key]; eventContainer.RemoveFromEvent(listener); } } public static string DebugInfo() { string info = string.Empty; foreach (var listener in GlobalListeners) { info += "     " + listener.Key.ToString() + "\n"; var t = (IDebugable)listener.Value; info += t.DebugInfo() + "\n"; } return info; } } public interface IDebugable { string DebugInfo(); } } 


لنبدأ مع الرئيسي

هذا هو القاموس الذي المفتاح هو نوع والقيمة حاوية

 public class EventContainer<T> : IDebugable 

 private static Dictionary<Type, object> GlobalListeners = new Dictionary<Type, object>(); 

لماذا نقوم بتخزين الحاوية ككائن؟ القاموس لا يعرف كيفية تخزين الأدوية الجنيسة. ولكن نظرًا للمفتاح ، فنحن قادرون على إحضار الكائن بسرعة إلى النوع الذي نحتاج إليه.

ماذا تحتوي الحاوية؟

 private event EventHandler<T> _eventKeeper; private readonly Dictionary<WeakReference, EventHandler<T>> _activeListenersOfThisType = new Dictionary<WeakReference, EventHandler<T>>(); 

يحتوي على multidelegate عام ومجموعة حيث المفتاح هو الكائن الذي هو المشترك ، والقيمة هي نفس طريقة المعالج. في الواقع ، يحتوي هذا القاموس على جميع الكائنات والأساليب التي تنتمي إلى هذا النوع. ونتيجة لذلك ، فإننا ندعو العديد من المناضلين ، ويدعو جميع المشتركين ، وهذا هو نظام الأحداث "صادقة" التي لا توجد فيها قيود على المشترك ، ولكن في معظم المجمعين الآخرين ، تحت غطاء محرك السيارة ، مجموعة من الفئات التي يتم تعميمها إما عن طريق واجهة خاصة أو الموروثة من فئة تنفذ النظام هو تكرار الرسائل.

عندما يتم استدعاء multidelegate ، يتم إجراء فحص لمعرفة ما إذا كانت هناك مفاتيح ميتة ، ويتم تنظيف المجموعة من الجثث ، ثم يتم أخذ multidelegate مع المشتركين المعنيين. هذا يستغرق بعض الوقت ، ولكن مرة أخرى ، في الواقع ، إذا تم فصل وظيفة الأحداث ، فعندئذ سيكون هناك حدث واحد من 3-5 مشتركين ، وبالتالي فإن الفحص ليس مخيفًا جدًا ، تكون فائدة الراحة أكثر وضوحًا. بالنسبة إلى قصص الشبكة حيث يمكن أن يكون هناك ألف مشترك أو أكثر ، من الأفضل عدم استخدام هذا المجمع. على الرغم من أن السؤال لا يزال مفتوحًا - إذا قمت بإزالة التحقق من الجثث ، وهو أسرع - فالتكرار عبر مجموعة من المشتركين من 1K أو الاتصال بمندوب متعدد من المشتركين 1K.

ميزات الاستخدام


من الأفضل دفع الاشتراك إلى Awake.

إذا كان الكائن قيد التشغيل / إيقاف التشغيل بشكل نشط ، فمن الأفضل الاشتراك في كل من Awake و OnEnable ، فلن يقوم بالتوقيع مرتين ، ولكن سيتم استبعاد احتمال أن يكون GameObject غير نشط للموت.

من الأفضل عدم بدء أحداث تحرير الفواتير قبل البدء ، حيث سيتم إنشاء وتسجيل جميع المشتركين.

مجمع يقوم بتنظيف القائمة في تفريغ المشهد. في بعض المجموعات ، يُقترح تنظيف تحميل المشهد - هذا ملف ، ويأتي حدث تحميل المشهد بعد Awake / OnEnable ، سيتم حذف المشتركين المضافة.

يحتوي المجمع - سلسلة ثابتة عامة DebugInfo () ، ويمكنك معرفة الفئات التي يتم الاشتراك في الأحداث.

مستودع جيثب

Source: https://habr.com/ru/post/ar446130/


All Articles