إذا كان مشروعك هو "مسرح" ، فاستخدم الممثلين

هناك قصة حول تجربة استخدام Actor Model في مشروع واحد مثير للاهتمام لتطوير نظام تحكم تلقائي للمسرح. أدناه سوف اقول انطباعاتي ، لا أكثر من ذلك.


منذ وقت ليس ببعيد شاركت في مهمة واحدة مثيرة: تحديث نظام التحكم الآلي (ACS) لرافعات الرافعة ، ولكن في الواقع كان تطوير ACS جديدًا.


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


لكن هذه مقالة تقنية ، وأريد أن أشارك تجربتي في استخدام Actor Model لكتابة نظام تحكم. وشارك انطباعاتي عن استخدام أحد أطر عمل الممثل لـ C ++: SObjectizer .


لماذا اخترنا هذا الإطار؟ لقد بحثنا في ذلك لفترة طويلة. هناك الكثير من المقالات باللغة الروسية ، ولها وثائق رائعة والعديد من الأمثلة. المشروع يبدو وكأنه واحد ناضجة. لقد أوضحت نظرة مختصرة على الأمثلة أن مطوري SObjectizer يستخدمون نفس المصطلحات (حالات ، توقيت ، أحداث ، وما إلى ذلك) ولم نتوقع حدوث مشكلات كبيرة في دراستها واستخدامها. وهناك عامل مهم آخر: فريق SObjectizer مفيد ومستعد دائمًا لمساعدتنا. لذلك قررنا أن نحاول.


ماذا نفعل؟


دعنا نتحدث عن هدف مشروعنا. نظام رافعات الضرب له 62 عروة (أنابيب معدنية). كل قرد ما دامت المرحلة بأكملها. يتم تعليقها على الحبال بالتوازي مع الثغرات من 30-40 سم ، بدءا من الحافة الأمامية للمرحلة. يمكن أن تثار كل أو سوط. يتم استخدام بعضها في عرض للمشهد. يتم تثبيت المشهد على البدن ويتم تحريكه لأعلى / لأسفل أثناء الأداء. أوامر من المشغلين بدء الحركة. يشبه نظام "موازنة حبل المحرك" النظام المستخدم في المصاعد في المباني السكنية. يتم وضع المحركات خارج المسرح ، لذلك لا يراها المتفرجون. جميع المحركات مقسمة إلى 8 مجموعات ، ولكل مجموعة 3 محولات تردد (FC). في معظم المحركات الثلاثة يمكن استخدامها في نفس الوقت في مجموعة ، كل واحد منهم متصل بـ FC منفصل. لذلك لدينا نظام من 62 محركا و 24 FCs ، وعلينا السيطرة على هذا النظام.


كانت مهمتنا هي تطوير واجهة الإنسان والآلة (HMI) للسيطرة على هذا النظام وتنفيذ خوارزميات التحكم. يتضمن النظام ثلاث محطات تحكم. يوجد اثنان منهم فوق المسرح ، وواحد في غرفة المحرك (يتم استخدام هذه المحطة من قبل كهربائي يعمل في الخدمة). هناك أيضا كتل التحكم مع وحدات التحكم في غرفة المحرك. تقوم وحدات التحكم هذه بتنفيذ أوامر التحكم ، والقيام بتعديل عرض النبض (PWM) ، أو تشغيل المحركات أو إيقاف تشغيلها ، والتحكم في موضع الضرب. تحتوي محطتان للتحكم أعلى المرحلة على شاشات العرض ووحدات النظام وكرات التتبع كأجهزة تأشير. محطات التحكم متصلة عبر الإيثرنت. يتم توصيل كل محطة التحكم مع كتل التحكم بواسطة قناة RS485. يمكن استخدام كلتا المحطتين فوق المسرح للتحكم في النظام في نفس الوقت ، ولكن يمكن أن تكون محطة واحدة فقط نشطة. يتم اختيار المحطة النشطة من قبل المشغل ؛ المحطة الثانية ستكون سلبية ؛ المحطة الخاملة لديها قناة RS485 تعطيل.


لماذا الممثلين؟


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


المبادئ الأساسية لمثل هذه الأنظمة هي: الفاعلون يتفاعلون عبر رسائل غير متزامنة ، والجهات الفاعلة لديها حالات وتتحول من حالة إلى أخرى ، فقط الرسائل التي لها معنى للحالة الحالية.


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


يوفر وجود المرسلين طريقة مرنة للغاية لضبط نظام فاعل لتلبية احتياجاتنا. يمكننا تجميع بعض الجهات الفاعلة للعمل على نفس السياق. يمكننا تغيير نوع المرسل فقط بسطر واحد من الكود. يقول مطورو SObjectizer أن كتابة مرسل مخصص ليست مهمة معقدة. ولكن لم تكن هناك حاجة لكتابة المرسل الخاص بنا في هذا المشروع ؛ تم العثور على كل ما نحتاجه في SObjectizer.


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


عند إنشاء ممثل ، تتم إضافته إلى التعاون (يمكن أن يحتوي التعاون على ممثل واحد فقط) ويكون مرتبطًا ببعض المرسل. من السهل إنشاء تعاونيات وممثلين ديناميكيين ، ويقول مطورو SObjectizer إنها عملية رخيصة إلى حد ما.


تتفاعل جميع الجهات الفاعلة مع بعضها البعض عبر "مربعات الرسائل" (mbox). إنه مفهوم SObjectizer آخر مثير للاهتمام وقوي. يوفر طريقة مرنة لمعالجة الرسائل.


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


في الثانية ، يتصور مطورو SObjectizer إمكانية إنشاء mbox المخصص. من السهل نسبيًا إنشاء mbox مخصص مع معالجة خاصة للرسائل الواردة (مثل التصفية أو الانتشار بين عدة مشتركين استنادًا إلى محتوى الرسالة).


يوجد أيضًا mbox شخصي لكل ممثل ويمكن للجهات الفاعلة تمرير إشارة إلى ذلك mbox في الرسائل إلى الجهات الفاعلة الأخرى (التي تسمح بالرد مباشرة على ممثل معين).


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


من الضروري الإشارة إلى أن SObjectizer-5 لا يوجد لديه آليات للتفاعل بين الشبكات و / و. هذا قرار واع لمطوري SObjectizer ؛ أرادوا جعل SObjectizer خفيفة الوزن قدر الإمكان. علاوة على ذلك ، كان هناك دعم شفاف للشبكات في بعض الإصدارات السابقة من SObjectizer ولكن تمت إزالته. لم يزعجنا ذلك لأن آلية التواصل تعتمد بشكل كبير على المهمة والبروتوكولات المستخدمة وغيرها من الشروط. لا يوجد حل عالمي واحد لجميع الحالات.


في حالتنا ، استخدمنا مكتبة libuniset2 القديمة الخاصة بنا للاتصالات والشبكات . نتيجة لذلك ، يدعم libuniset2 الاتصالات مع أجهزة الاستشعار وكتل التحكم ، ويدعم SObjectizer الجهات الفاعلة والتفاعلات بين العناصر الفاعلة داخل عملية واحدة.


كما قلت في وقت سابق هناك 62 محركات. يمكن توصيل كل محرك بـ FC (محول التردد) ؛ يمكن تحديد إحداثيات الوجهة للبطين المقابل ؛ يمكن أيضا تحديد سرعة حركة البطين. وكإضافة إلى ذلك ، يحتوي كل محرك على الحالات التالية:


  • جاهز للعمل
  • اتصال.
  • العمل.
  • عطل.
  • الاتصال (حالة انتقالية) ؛
  • قطع الاتصال (حالة انتقالية) ؛

يتم تمثيل كل محرك في النظام بواسطة ممثل يقوم بتنفيذ الانتقال بين الحالات ، والتعامل مع البيانات من أجهزة الاستشعار وأوامر الإصدار. ليس من الصعب إنشاء ممثل في SObjectizer: فقط so_5::agent_t من so_5::agent_t type. يجب أن تكون الوسيطة الأولى context_t الفاعل من النوع context_t ، ويمكن تعريف جميع الوسائط الأخرى كما يريد المطور.


 class Drive_A: public so_5::agent_t { public: Drive_A( context_t ctx, ... ); ... } 

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


ما هي "حالة" الممثل؟ عن ماذا نتحدث؟


يعد استخدام الحالات والانتقال بينها "موضوعًا أصليًا" لأنظمة التحكم. هذا المفهوم هو جيد جدا للتعامل مع الحدث. يتم دعم هذا المفهوم في SObjectizer على مستوى API. يتم إعلان الدول داخل فئة الممثل:


 class Drive_A final: public so_5::agent_t { public: Drive_A( context_t ctx, ... ); virtual ~Drive_A(); //  state_t st_base {this}; state_t st_disabled{ initial_substate_of{st_base}, "disabled" }; state_t st_preinit{ substate_of{st_base}, "preinit" }; state_t st_off{ substate_of{st_base}, "off" }; state_t st_connecting{ substate_of{st_base}, "connecting" }; state_t st_disconnecting{ substate_of{st_base}, "disconnecting" }; state_t st_connected{ substate_of{st_base}, "connected" }; ... } 

ومن ثم يتم تعريف معالجات الأحداث لكل ولاية. في بعض الأحيان يكون من الضروري عمل شيء ما عند الدخول إلى الدولة أو الخروج منها. يتم دعم هذا أيضًا في SObjectizer من خلال معالجات on_enter / on_exit. يبدو أن مطوري SObjectizer لديهم خلفية في تطوير أنظمة التحكم.


معالجات الأحداث


معالج الأحداث هو المكان الذي يتم فيه تطبيق منطق التطبيق الخاص بك. كما قلت سابقًا ، يتم إنشاء اشتراك لمجموعة mbox معينة وحالة معينة. إذا لم يكن للممثل أي حالات محددة صراحة ، فهو في "default_state" خاص.


يمكن تعريف معالجات مختلفة لنفس الحدث في حالات مختلفة. إذا لم تحدد معالجًا لحدث ما ، فسيتم تجاهل هذا الحدث (لن يعرفه أحد الممثلين).


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


 so_subscribe(drv->so_mbox()) .in(st_base) .event( &Drive_A::on_get_info ) .event( &Drive_A::on_control ) .event( &Drive_A::off_control ); 

إنه مثال على الاشتراك في الأحداث من mbox معين في حالة st_base. تجدر الإشارة إلى أن st_base هي حالة أساسية لبعض الحالات الأخرى وأن هذا الاشتراك سيتم توريثه بواسطة حالات مشتقة. يسمح هذا الأسلوب بالتخلص من النسخ واللصق لمعالجات الأحداث المماثلة في حالات مختلفة. لكن يمكن إعادة تعريف معالج الأحداث الموروثة لحالة معينة أو يمكن تعطيل الحدث بالكامل ("تم كبحه").


هناك طريقة أخرى لتعريف معالجات الأحداث وهي استخدام وظائف lambda. إنها طريقة مريحة للغاية لأن معالجات الأحداث غالباً ما تحتوي فقط على سطر أو اثنين من التعليمات البرمجية: إرسال شيء إلى مكان ما أو تغيير الحالة:


 so_subscribe(drv->so_mbox()) .in(st_disconnecting) .event([this](const msg_disconnected_t& m) { ... st_off.activate(); }) .event([this]( const msg_failure_t& m ) { ... st_protection.activate(); }); 

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


راجع للشغل ، للحالات المباشرة ، حيث يكون انتقال الحالة ضروريًا فقط ، يوجد بناء جملة خاص:


 auto mbox = drv->so_mbox(); st_off .just_switch_to<msg_connected_t>(mbox, st_connected) .just_switch_to<msg_failure_t>(mbox, st_protection) .just_switch_to<msg_on_limit_t>(mbox, st_protection) .just_switch_to<msg_on_t>(mbox, st_on); 

السيطرة


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


هناك وضعان للتحكم: اليدوي و "وضع السيناريو". ستتم مناقشة "وضع السيناريو" لاحقًا ، والآن دعنا نتحدث عن الوضع اليدوي. في هذا الوضع ، يختار المشغل عنصرًا ، ويقوم بإعداده للحركة (يربط المحرك بـ FC) ، ويحدد العلامة المستهدفة للضربة ، وعندما يتم ضبط السرعة فوق الصفر ، يبدأ الضرب في التحرك.


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


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


وضع السيناريو (هل هناك ممثلون مرة أخرى؟)


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


  • تحضير (يتم توصيل مجموعة من المحركات بـ FC) ؛
  • اذهب (حركة المجموعة تبدأ).

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


ممثل نموذج يناسب هذه القضية تماما. لقد طورنا "لاعب سيناريو" يولد مجموعة من الممثلين الخاصين ويبدأهم. لقد قمنا بتطوير نوعين من الجهات الفاعلة: الجهات الفاعلة المنفذون (يتحكمون في حركة الضربات) والممثلين المنسقين (يقومون بتوزيع المهام بين المنفذين). يتم إنشاء المنفذين عند الطلب: عندما لا يكون هناك مدراء تنفيذيين مجانيين ، سيتم إنشاء منفذ جديد. منسق يدير مجموعة من المسؤولين التنفيذيين المتاحة. نتيجة لذلك ، يبدو عنصر التحكم كما يلي تقريبًا:


  • مشغل يحمل سيناريو ؛
  • "بالتمرير" حتى جدول الأعمال المطلوب ؛
  • يضغط على زر "الاستعداد" في الوقت المناسب. في تلك اللحظة يتم إرسال رسالة إلى المنسق. تحتوي هذه الرسالة على بيانات لكل قاتل من جدول الأعمال ؛
  • يقوم المنسق بمراجعة مجموعة من المنفذين ويوزع المهام بين المنفذين المجانيين (يتم إنشاء منفذين جدد ، إذا لزم الأمر) ؛
  • يستقبل كل منفّذ مهمة وينفذ إجراءات التحضير (يربط محركًا بـ FC ، ثم ينتظر أمر "go") ؛
  • يضغط المشغل على زر "go" في اللحظة المناسبة ؛
  • ينتقل الأمر "go" إلى المنسق ، ويقوم بتوزيع الأمر بين جميع المسؤولين التنفيذيين قيد الاستخدام حاليًا.

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


عندما يصل الضرب بنجاح إلى العلامة المستهدفة (أو يكون هناك عطل) ، يقوم المسؤول التنفيذي بإبلاغ المنسق عن إتمام المهمة. يرد المنسق بأمر لإيقاف تشغيل المحرك (إذا لم يشارك البطين في جدول الأعمال بعد الآن) أو يرسل مهمة جديدة إلى المنفذ. يقوم المشغّل بإيقاف تشغيل المحرك ويتحول إلى حالة "انتظار" أو يبدأ في معالجة الأمر الجديد.


نظرًا لأن SObjectizer يحتوي على واجهة برمجة تطبيقات (API) مدروسة ومناسبة تمامًا للعمل مع الحالات ، فقد تبين أن رمز التنفيذ هو موجز للغاية. على سبيل المثال ، يتم وصف التأخير قبل الحركة بسطر من التعليمات البرمجية فقط:


 st_delay.time_limit( std::chrono::milliseconds{target->delay()}, st_moving ); st_delay.activate(); ... 

تحدد طريقة time_limit مقدار الوقت للبقاء في الولاية والحالة التي ينبغي تنشيطها بعد ذلك ( st_moving في هذا المثال).


الجهات الفاعلة الحماية


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


  • حماية التيار الزائد.
  • الحماية من عطل المستشعر ؛
  • الحماية من الحركة في الاتجاه المعاكس (يمكن أن يحدث ذلك إذا حدث خطأ ما في أجهزة الاستشعار أو المشغلات) ؛
  • الحماية من الحركة التلقائية (بدون أمر) ؛
  • مراقبة تنفيذ الأوامر (يجب فحص حركة الضرب).

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


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


كما الاستنتاج ...


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


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


ممثل النموذج يعمل حقا! خاصة في المسرح.


المقالة الأصلية باللغة الروسية

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


All Articles