Jika proyek Anda adalah "Teater" maka gunakan aktor

Ada sebuah kisah tentang pengalaman menggunakan Model Aktor dalam satu proyek menarik mengembangkan sistem kontrol otomatis untuk teater. Di bawah ini saya akan memberi tahu kesan saya, tidak lebih dari itu.


Belum lama ini saya berpartisipasi dalam satu tugas menarik: modernisasi sistem kontrol otomatis (ACS) untuk batten hoist, tetapi sebenarnya itu adalah pengembangan ACS baru.


Teater modern (terutama jika teater itu besar) adalah organisasi yang sangat kompleks. Ada banyak orang, berbagai mekanisme dan sistem. Salah satu sistem tersebut adalah ACS untuk penanganan pengangkatan dan pengaturan pemandangan. Pertunjukan modern, seperti opera dan balet, semakin banyak menggunakan sarana teknis dari tahun ke tahun. Pemandangan ini secara aktif digunakan oleh sutradara pertunjukan dan bahkan memainkan peran penting sendiri. Sangat menarik untuk menemukan apa yang terjadi di balik tirai karena penonton biasa hanya dapat melihat aksi di tempat kejadian.


Tetapi ini adalah artikel teknis, dan saya ingin berbagi pengalaman saya menggunakan Model Aktor untuk menulis sistem kontrol. Dan bagikan kesan saya menggunakan salah satu kerangka aktor untuk C ++: SObjectizer .


Mengapa kami memilih kerangka kerja ini? Kami sudah lama melihatnya. Ada banyak artikel dalam bahasa Rusia, dan memiliki dokumentasi yang luar biasa dan banyak contoh. Proyek ini terlihat seperti proyek yang sudah matang. Melihat contoh-contoh secara singkat telah menunjukkan bahwa pengembang SObjectizer menggunakan istilah yang sama (status, penghitung waktu, peristiwa, dll.) Dan kami tidak mengharapkan masalah besar untuk mempelajari dan menggunakannya. Dan faktor penting lainnya: Tim SObjectizer sangat membantu dan selalu siap membantu kami. Jadi kami memutuskan untuk mencoba.


Apa yang kita lakukan


Mari kita bicara tentang target proyek kita. Sistem reng hoen memiliki 62 reng (tabung logam). Setiap reng sepanjang tahap. Mereka tergantung pada tali secara paralel dengan celah 30-40cm, mulai dari tepi depan panggung. Setiap reng bisa dinaikkan atau diturunkan. Beberapa dari mereka digunakan dalam pertunjukan untuk pemandangan. Pemandangan ditetapkan pada reng dan digerakkan naik / turun selama pertunjukan. Perintah dari operator memulai pergerakan. Sistem "engine-rope-counterbalance" mirip dengan yang digunakan pada elevator di bangunan tempat tinggal. Mesin ditempatkan di luar panggung, sehingga penonton tidak melihatnya. Semua mesin dibagi menjadi 8 kelompok, dan masing-masing kelompok memiliki 3 konverter frekuensi (FC). Paling banyak tiga mesin dapat digunakan pada saat yang sama dalam sebuah grup, masing-masing terhubung ke FC terpisah. Jadi kami memiliki sistem 62 mesin dan 24 FC, dan kami harus mengendalikan sistem ini.


Tugas kami adalah mengembangkan antarmuka manusia-mesin (HMI) untuk mengendalikan sistem ini dan mengimplementasikan algoritma kontrol. Sistem ini mencakup tiga stasiun kontrol. Dua dari mereka ditempatkan tepat di atas panggung, dan satu di ruang mesin (stasiun ini digunakan oleh tukang listrik yang bertugas). Ada juga blok kontrol dengan pengontrol di ruang mesin. Kontroler ini melakukan perintah kontrol, melakukan modulasi lebar-pulsa (PWM), menghidupkan atau mematikan mesin, mengontrol posisi reng. Dua stasiun kontrol di atas panggung memiliki layar, unit sistem, dan trackball sebagai perangkat penunjuk. Stasiun kontrol terhubung melalui Ethernet. Setiap stasiun kontrol terhubung dengan blok kontrol oleh saluran RS485. Kedua stasiun di atas panggung dapat digunakan untuk mengontrol sistem secara bersamaan, tetapi hanya satu stasiun yang dapat aktif. Stasiun aktif dipilih oleh operator; stasiun kedua akan pasif; stasiun pasif memiliki saluran RS485 dinonaktifkan.


Mengapa Aktor?


Dari sudut pandang algoritma, sistem dibangun di atas peristiwa. Data dari sensor, tindakan operator, kedaluwarsa timer ... Ini semua adalah contoh peristiwa. Model Aktor bekerja dengan baik untuk algoritme seperti itu: aktor menangani peristiwa yang masuk dan membentuk beberapa tindakan keluar tergantung pada keadaan mereka saat ini. Mekanika ini tersedia dalam SObjectizer di luar kotak.


Prinsip-prinsip dasar untuk sistem tersebut adalah: aktor berinteraksi melalui pesan asinkron, aktor memiliki negara dan beralih dari satu negara ke negara lain, hanya pesan yang bermakna untuk keadaan saat ini ditangani.


Sangat menarik bahwa aktor dipisahkan dari utas pekerja di SObjectizer. Ini berarti bahwa Anda dapat menerapkan dan men-debug aktor Anda terlebih dahulu dan baru kemudian memutuskan utas pekerja apa yang akan digunakan untuk setiap aktor. Ada "Dispatcher" yang menerapkan berbagai kebijakan terkait utas. Misalnya, ada operator yang menyediakan utas pekerja terpisah untuk masing-masing aktor; ada dispatcher thread-pool yang menyediakan kumpulan thread pekerja yang berukuran tetap; ada operator yang menjalankan semua aktor pada utas yang sama.


Kehadiran operator menyediakan cara yang sangat fleksibel untuk mengatur sistem aktor untuk kebutuhan kita. Kita dapat mengelompokkan beberapa aktor untuk bekerja pada konteks yang sama. Kami dapat mengubah jenis dispatcher hanya dengan satu baris kode. Pengembang SObjectizer mengatakan bahwa menulis dispatcher khusus bukanlah tugas yang rumit. Tetapi tidak perlu menulis dispatcher kita sendiri dalam proyek ini; semua yang kami butuhkan ditemukan di SObjectizer.


Namun fitur lain yang menarik adalah kerjasama aktor. Kerja sama adalah sekelompok aktor yang dapat eksis jika dan hanya jika semua aktor telah mulai berhasil. Kerjasama tidak dapat dimulai jika setidaknya salah satu aktornya gagal memulai. Tampaknya ada analogi antara kerja sama SObjectizer dan polong dari Kubernetes, tetapi tampaknya juga kerja sama SObjectizer telah muncul sebelumnya ...


Ketika seorang aktor dibuat itu ditambahkan ke kerja sama (kerja sama dapat berisi hanya satu aktor) dan terikat dengan beberapa operator. Sangat mudah untuk membuat kerjasama dan aktor secara dinamis dan pengembang SObjectizer mengatakan bahwa ini adalah operasi yang agak murah.


Semua aktor berinteraksi satu sama lain melalui "kotak pesan" (mbox). Ini adalah konsep SObjectizer lain yang menarik dan kuat. Ini memberikan cara pemrosesan pesan yang fleksibel.


Pada awalnya, mungkin ada lebih dari satu penerima pesan di belakang mbox. Ini cukup membantu. Misalnya, mungkin ada mbox yang digunakan oleh sensor untuk menerbitkan data baru. Aktor dapat membuat langganan untuk mbox itu, dan aktor yang berlangganan akan menerima data yang mereka inginkan. Ini memungkinkan bekerja dengan cara "Terbitkan / Berlangganan".


Kedua, pengembang SObjectizer telah mempertimbangkan kemungkinan pembuatan mbox khusus. Relatif mudah membuat mbox khusus dengan pemrosesan khusus pesan masuk (seperti pemfilteran atau penyebaran di antara beberapa pelanggan berdasarkan konten pesan).


Ada juga mbox pribadi untuk setiap aktor dan aktor dapat memberikan referensi ke mbox itu dalam pesan kepada aktor lain (yang memungkinkan untuk membalas langsung ke aktor tertentu).


Dalam proyek kami, kami membagi semua objek yang dikendalikan menjadi delapan kelompok (satu kelompok untuk setiap kotak kontrol). Tiga utas pekerja dibuat untuk setiap grup (karena hanya tiga mesin yang dapat bekerja pada saat yang bersamaan). Itu memungkinkan kami untuk memiliki independensi di antara kelompok-kelompok mesin. Itu juga diizinkan untuk bekerja secara tidak sinkron dengan mesin di dalam masing-masing kelompok.


Perlu disebutkan bahwa SObjectizer-5 tidak memiliki mekanisme untuk interprocess atau / dan interaksi jaringan. Ini adalah keputusan sadar pengembang SObjectizer; mereka ingin membuat SObjectizer seringan mungkin. Selain itu, dukungan transparan untuk jaringan telah ada di beberapa versi sebelumnya dari SObjectizer tetapi telah dihapus. Itu tidak mengganggu kami karena mekanisme untuk jaringan sangat tergantung pada tugas, protokol yang digunakan dan kondisi lainnya. Tidak ada solusi universal tunggal untuk semua kasus.


Dalam kasus kami, kami menggunakan libuniset2 perpustakaan lama kami untuk komunikasi jaringan dan antar proses. Akibatnya, libuniset2 mendukung komunikasi dengan sensor dan blok kontrol, dan SObjectizer mendukung aktor dan interaksi antara aktor di dalam satu proses tunggal.


Seperti yang saya katakan sebelumnya ada 62 mesin. Setiap mesin dapat dihubungkan ke FC (konverter frekuensi); koordinat tujuan dapat ditentukan untuk reng yang sesuai; kecepatan gerakan reng juga bisa ditentukan. Dan sebagai tambahan untuk itu, setiap mesin memiliki status berikut:


  • siap bekerja;
  • terhubung;
  • bekerja;
  • kerusakan;
  • menghubungkan (keadaan transisi);
  • disconnecting (keadaan transisi);

Setiap mesin diwakili dalam sistem oleh aktor yang mengimplementasikan transisi antar negara, menangani data dari sensor dan mengeluarkan perintah. Tidak sulit untuk membuat aktor di SObjectizer: cukup mewarisi kelas Anda dari so_5::agent_t . Argumen pertama dari konstruktor aktor harus bertipe context_t , semua argumen lain dapat didefinisikan sesuai keinginan pengembang.


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

Saya tidak akan menunjukkan penjelasan rinci tentang kelas dan metode karena ini bukan tutorial. Saya hanya ingin menunjukkan betapa mudahnya semuanya dapat dilakukan di SObjectizer (dalam beberapa baris secara harfiah). Biarkan saya mengingatkan Anda bahwa SObjectizer memiliki dokumentasi yang sangat baik dan banyak contoh.


Apa "keadaan" aktor? Apa yang kita bicarakan


Penggunaan status dan transisi di antara keduanya adalah "topik asli" untuk sistem kontrol. Konsep ini sangat baik untuk penanganan acara. Konsep ini didukung dalam SObjectizer di tingkat API. Negara dideklarasikan di dalam kelas aktor:


 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" }; ... } 

dan kemudian event-handler didefinisikan untuk setiap negara. Terkadang perlu untuk melakukan sesuatu saat memasuki atau keluar dari negara. Ini juga didukung di SObjectizer melalui penangan on_enter / on_exit. Tampaknya pengembang SObjectizer memiliki latar belakang dalam pengembangan sistem kontrol.


Penangan acara


Event handler adalah tempat di mana logika aplikasi Anda diimplementasikan. Seperti yang saya katakan sebelumnya, langganan dibuat untuk mbox tertentu dan negara tertentu. Jika seorang aktor tidak memiliki status yang ditentukan secara eksplisit, ia berada dalam "default_state" khusus.


Penangan yang berbeda dapat didefinisikan untuk acara yang sama di negara yang berbeda. Jika Anda tidak mendefinisikan handler untuk beberapa acara, maka acara ini akan diabaikan (aktor tidak akan mengetahuinya).


Ada sintaksis sederhana untuk mendefinisikan event handler. Anda menentukan metode, dan tidak perlu menentukan tipe atau parameter templat tambahan. Sebagai contoh:


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

Ini adalah contoh berlangganan acara dari mbox tertentu di st_base state. Perlu disebutkan bahwa st_base adalah keadaan dasar untuk beberapa negara bagian lain dan langganan itu akan diwarisi oleh negara bagian yang diturunkan. Pendekatan ini memungkinkan untuk menyingkirkan copy-dan-paste untuk penangan acara yang serupa di negara bagian yang berbeda. Tetapi event handler yang diwarisi dapat didefinisikan ulang untuk keadaan tertentu atau acara dapat sepenuhnya dinonaktifkan ("ditekan").


Cara lain untuk mendefinisikan event handler adalah menggunakan fungsi lambda. Ini adalah cara yang sangat mudah karena penangan acara sering hanya berisi satu atau dua baris kode: pengiriman sesuatu ke suatu tempat atau perubahan status:


 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(); }); 

Sintaks itu terlihat rumit pada awalnya, tetapi menjadi akrab setelah beberapa hari pengkodean aktif dan Anda bahkan mulai menyukainya. Itu karena seluruh logika beberapa aktor bisa ringkas dan ditempatkan dalam satu layar. Dalam contoh yang ditunjukkan di atas, ada transisi dari st_disconnected ke st_off atau st_protection. Kode ini mudah dibaca.


BTW, untuk kasus langsung, di mana hanya transisi negara diperlukan, ada sintaks khusus:


 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); 

Kontrol


Bagaimana kontrolnya diatur? Seperti disebutkan di atas ada dua stasiun kontrol untuk mengendalikan pergerakan reng. Setiap stasiun kontrol memiliki layar, alat penunjuk (trackball) dan penentu kecepatan (dan kami tidak menghitung komputer di dalam stasiun dan beberapa aksesori tambahan).


Ada dua mode kontrol: manual dan "mode skenario". "Mode Skenario" akan dibahas nanti, dan sekarang mari kita bicara tentang mode manual. Dalam mode ini, seorang operator memilih reng, menyiapkannya untuk gerakan (menghubungkan mesin ke FC), menetapkan tanda target untuk reng, dan ketika kecepatan diatur di atas nol, reng mulai bergerak.


Penyetel kecepatan adalah aksesori fisik dalam bentuk "potensiometer dengan gagang", tetapi ada juga yang virtual yang ditampilkan di layar stasiun. Semakin diputar, semakin tinggi kecepatan gerakannya. Kecepatan maksimum dibatasi 1,5 meter per detik. Penyetel kecepatan adalah satu untuk semua reng. Ini berarti bahwa semua reng yang dipilih bergerak dengan kecepatan yang sama. Reng bisa bergerak ke arah yang berlawanan (tergantung pilihan operator). Jelas bahwa sulit bagi manusia untuk mengendalikan lebih dari beberapa reng. Karena itu hanya kelompok kecil reng yang ditangani dalam mode manual. Operator dapat mengontrol reng dari dua stasiun kontrol secara bersamaan. Jadi ada setter kecepatan terpisah untuk setiap stasiun.


Dari sudut pandang implementasi, tidak ada logika khusus dalam mode manual. Perintah "menghubungkan mesin" pergi dari antarmuka grafis, diubah menjadi pesan yang sesuai dengan aktor, dan kemudian sedang ditangani oleh aktor itu. Aktor beralih dari status "tidak aktif" ke "menghubungkan", dan kemudian ke status "terhubung". Hal serupa terjadi dengan perintah untuk memposisikan batten dan mengatur kecepatan gerakan. Semua perintah ini diteruskan ke aktor dalam bentuk pesan. Tetapi perlu disebutkan bahwa "antarmuka grafis" dan "proses kontrol" adalah proses yang terpisah dan libuniset2 digunakan untuk IPC.


Mode Skenario (apakah ada Aktor lagi?)


Dalam praktiknya, mode manual hanya digunakan untuk kasus yang sangat sederhana atau selama latihan. Mode kontrol utama adalah "mode skenario". Dalam mode itu, setiap reng dipindahkan ke posisi tertentu dengan kecepatan tertentu sesuai dengan pengaturan skenario. Dua perintah sederhana tersedia untuk operator dalam mode itu:


  • mempersiapkan (sekelompok mesin sedang terhubung ke FC);
  • go (pergerakan grup dimulai).

Seluruh skenario dibagi menjadi "agenda". "Agenda" menggambarkan satu gerakan sekelompok reng. Ini berarti bahwa "agenda" termasuk beberapa reng dan berisi tujuan target dan kecepatan untuk mereka. Pada kenyataannya, sebuah skenario terdiri dari tindakan, tindakan terdiri dari gambar, gambar terdiri dari agenda, dan agenda terdiri dari target untuk reng. Tetapi dari sudut pandang kontrol, itu tidak masalah, karena hanya agenda yang mengandung parameter yang tepat dari pergerakan batal.


Model Aktor sangat cocok dengan case itu. Kami telah mengembangkan "pemain skenario" yang memunculkan sekelompok aktor khusus dan memulai mereka. Kami telah mengembangkan dua jenis aktor: aktor pelaksana (mereka mengendalikan pergerakan reng) dan aktor koordinator (mereka mendistribusikan tugas di antara para pelaksana). Pelaksana dibuat atas permintaan: ketika tidak ada pelaksana gratis, pelaksana baru akan dibuat. Koordinator mengelola kumpulan pelaksana yang tersedia. Akibatnya, kontrolnya kira-kira terlihat seperti ini:


  • seorang operator memuat skenario;
  • "gulir" itu sampai agenda yang diperlukan;
  • tekan tombol "siapkan" pada waktu yang tepat. Pada saat itu pesan dikirim ke koordinator. Pesan ini berisi data untuk setiap reng dari agenda;
  • koordinator meninjau kumpulan pelaksana dan mendistribusikan tugas-tugas antara pelaksana bebas (pelaksana baru dibuat, jika perlu);
  • setiap pelaksana menerima tugas dan melakukan tindakan persiapan (menghubungkan mesin ke FC, kemudian menunggu perintah "go");
  • operator menekan tombol "pergi" pada saat yang tepat;
  • perintah "go" pergi ke koordinator, dan mendistribusikan perintah antara semua pelaksana yang saat ini digunakan.

Ada beberapa parameter tambahan dalam agenda. Seperti "mulai gerakan hanya setelah N detik tertunda" atau "mulai gerakan hanya setelah perintah tambahan dari operator". Karena itu, daftar negara bagian untuk pelaksana cukup panjang: "siap untuk perintah berikutnya", "siap untuk gerakan", "penundaan gerakan", "menunggu perintah operator", "bergerak", "selesai", "kegagalan".


Ketika reng telah berhasil mencapai tanda target (atau ada kegagalan) pelaksana melaporkan kepada koordinator tentang penyelesaian tugas. Koordinator menjawab dengan perintah untuk mematikan mesin (jika batal tidak berpartisipasi dalam agenda lagi) atau mengirim tugas baru kepada pelaksana. Eksekutor mematikan mesin dan beralih ke status "menunggu" atau mulai memproses perintah baru.


Karena SObjectizer memiliki API yang cukup bijaksana dan nyaman untuk bekerja dengan status, kode implementasi ternyata cukup ringkas. Misalnya, penundaan sebelum perpindahan dijelaskan hanya dengan satu baris kode:


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

Metode time_limit menentukan jumlah waktu untuk tetap dalam status dan kondisi mana yang harus diaktifkan kemudian ( st_moving dalam contoh itu).


Aktor perlindungan


Tentu saja, kegagalan dapat terjadi. Ada persyaratan untuk menangani kegagalan ini dengan benar. Aktor juga digunakan untuk tugas seperti itu. Mari kita lihat beberapa contoh:


  • perlindungan arus lebih;
  • perlindungan dari kerusakan sensor;
  • perlindungan dari gerakan dalam arah yang berlawanan (itu bisa terjadi jika ada sesuatu yang salah dengan sensor atau aktuator);
  • perlindungan dari gerakan spontan (tanpa perintah);
  • kontrol pelaksanaan perintah (pergerakan reng harus diperiksa).

Kita dapat melihat bahwa semua kasus itu swasembada, tetapi mereka harus dikontrol bersama, pada saat yang sama. Ini berarti bahwa kegagalan dapat terjadi. Tetapi setiap cek memiliki logikanya: kadang-kadang perlu untuk memeriksa batas waktu, kadang-kadang diperlukan untuk menganalisis beberapa nilai sebelumnya dari sensor. Karena itu, perlindungan dilaksanakan dalam bentuk aktor kecil. Aktor-aktor ini ditambahkan pada kerjasama dengan aktor utama yang mengimplementasikan logika kontrol. Pendekatan ini memungkinkan penambahan kasus perlindungan baru dengan mudah: tambahkan saja aktor pelindung lain ke dalam kerja sama. Kode aktor seperti itu biasanya singkat dan mudah dimengerti, karena hanya mengimplementasikan satu fungsi.


Aktor pelindung juga memiliki beberapa negara. Biasanya, mereka dihidupkan ketika mesin dihidupkan atau ketika sebuah reng mulai bergerak. Ketika pelindung mendeteksi kegagalan / kegagalan, ia menerbitkan pemberitahuan (dengan kode perlindungan dan beberapa detail tambahan di dalamnya). Aktor utama bereaksi terhadap pemberitahuan itu dan melakukan tindakan yang diperlukan (seperti mematikan mesin dan beralih ke keadaan terlindungi).


Sebagai kesimpulan ...


... artikel ini bukan merupakan terobosan tentunya. Model Aktor digunakan dalam berbagai sistem untuk waktu yang cukup lama. Tetapi itu adalah pengalaman pertama saya menggunakan Model Aktor untuk membangun sistem kontrol otomatis dalam proyek yang agak kecil. Dan pengalaman ini ternyata cukup sukses. Saya harap saya telah menunjukkan bahwa aktor cocok untuk algoritma kontrol: ada tempat untuk aktor di mana-mana.


Kami telah menerapkan sesuatu yang serupa dalam proyek-proyek sebelumnya (maksud saya negara bagian, pertukaran pesan, pengelolaan utas pekerja, dan sebagainya), tetapi itu bukan pendekatan terpadu. Dengan menggunakan SObjectizer, kami mendapat alat kecil dan ringan yang memecahkan banyak masalah. Kita tidak perlu lagi (secara eksplisit) menggunakan mekanisme sinkronisasi tingkat rendah (seperti mutex), tidak ada manajemen thread manual, tidak ada lagi statecharts tulisan tangan. Semua ini disediakan oleh framework, terhubung secara logis dan diekspresikan dalam bentuk API yang nyaman, tetapi Anda tidak kehilangan kontrol pada detail. Jadi itu pengalaman yang menyenangkan. Jika Anda masih ragu, maka saya sarankan Anda untuk melihat Actor Model dan SObjectizer pada khususnya. Itu meninggalkan emosi positif.


Model Aktor benar-benar berfungsi! Terutama di teater.


Artikel asli dalam bahasa Rusia

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


All Articles