Mari kita coba berbicara tentang mesin status hierarkis secara umum dan dukungannya pada SObjectizer-5 pada khususnya

Mesin negara yang terbatas mungkin adalah salah satu konsep yang paling mendasar dan banyak digunakan dalam pemrograman. Mesin keadaan terbatas (KA) secara aktif digunakan di banyak relung terapan. Secara khusus, dalam relung seperti APCS dan telekomunikasi, yang memungkinkan untuk berurusan, pesawat ruang angkasa ditemukan sedikit lebih jarang daripada di setiap langkah.

Oleh karena itu, dalam artikel ini kita akan mencoba untuk berbicara tentang pesawat ruang angkasa, terutama tentang mesin negara hingga hirarkis dan kemampuan canggihnya. Dan ceritakan sedikit tentang dukungan untuk pesawat ruang angkasa di SObjectizer-5 , kerangka "aktor" untuk C ++. Salah satu dari dua yang terbuka, bebas, lintas platform, dan masih hidup.

Sekalipun Anda tidak tertarik pada SObjectizer, tetapi Anda belum pernah mendengar tentang mesin negara berhingga hingga hirarkis atau seberapa bermanfaat fitur canggih seperti pesawat ruang angkasa sebagai penangan input / output untuk negara atau riwayat status, maka Anda mungkin tertarik untuk mencari di bawah kucing dan baca setidaknya bagian pertama artikel.

Kata-kata umum tentang mesin negara hingga


Kami tidak akan mencoba untuk melakukan program pendidikan penuh dalam artikel tentang topik automata dan variasi seperti mesin negara yang terbatas . Pembaca perlu memiliki setidaknya pemahaman dasar tentang jenis-jenis entitas ini.

Mesin negara hingga canggih dan kemampuannya


Wahana antariksa ini memiliki beberapa fitur "canggih" yang sangat meningkatkan kegunaan wahana antariksa dalam program tersebut. Mari kita lihat fitur-fitur "canggih" ini.

Penafian: jika pembaca mengenal diagram negara dari UML, maka dia tidak akan menemukan sesuatu yang baru untuk dirinya di sini.

Mesin negara hirarkis


Mungkin peluang yang paling penting dan berharga adalah pengorganisasian hierarki / persarangan negara. Karena justru kemampuan untuk menempatkan negara ke satu sama lain yang menghilangkan "ledakan" jumlah transisi dari negara ke negara saat kompleksitas pesawat ruang angkasa meningkat.

Lebih sulit untuk menjelaskan hal ini dengan kata-kata daripada menunjukkan dengan contoh. Karena itu, mari kita bayangkan bahwa kita memiliki infokiosk di layar di mana pesan selamat datang pertama kali ditampilkan. Pengguna dapat memilih item "Layanan" dan pergi ke bagian untuk memilih layanan yang ia butuhkan. Atau dia dapat memilih item "Akun Pribadi" dan pergi ke bagian bekerja dengan data dan layanan pribadinya. Atau dia dapat memilih bagian Bantuan. Sejauh ini, semuanya tampak sederhana dan dapat diwakili oleh diagram keadaan berikut (sesederhana mungkin):



Tetapi mari kita coba untuk memastikan bahwa dengan mengklik tombol "Batal", pengguna dapat kembali dari bagian mana saja ke halaman awal dengan pesan selamat datang:



Skema ini semakin rumit, tetapi masih terkendali. Namun, mari kita ingat bahwa di bagian "Layanan" kami mungkin memiliki beberapa subbagian lagi, misalnya, "Layanan Populer", "Layanan Baru" dan "Daftar Lengkap". Dan dari masing-masing bagian ini Anda juga perlu kembali ke halaman awal. Pesawat ruang angkasa sederhana kami menjadi semakin sulit:



Tapi ini jauh dari semua. Kami belum memperhitungkan tombol "Kembali", yang dengannya kami harus kembali ke bagian sebelumnya. Mari kita tambahkan reaksi ke tombol "Kembali" dan lihat apa yang kita dapatkan:



Ya, sekarang kita melihat jalan menuju kesenangan nyata. Tapi kami bahkan belum mempertimbangkan subbagian di bagian "Akun Saya" dan "Bantuan" ... Jika kita mulai, maka segera saja pesawat ruang angkasa kita yang sederhana, pada awalnya, akan berubah menjadi sesuatu yang tak terbayangkan.

Di sini, persarangan negara bagian datang untuk membantu kami. Mari kita bayangkan bahwa kita hanya memiliki dua status tingkat atas: WelcomeScreen dan UserSelection. Semua bagian kami (yaitu, "Layanan", "Akun Saya" dan "Bantuan") akan "disarangkan" di negara bagian UserSelection. Anda dapat mengatakan bahwa status ServicesScreen, ProfileScreen, dan HelpScreen akan menjadi anak-anak dari UserSelection. Dan karena mereka adalah anak-anak, mereka akan mewarisi reaksi terhadap beberapa sinyal dari keadaan orang tua mereka. Oleh karena itu, kita dapat menentukan respons terhadap tombol Batal di UserSelection. Tetapi kita tidak perlu menentukan reaksi ini di semua cabang pembantu. Apa yang membuat pesawat ruang angkasa kita lebih ringkas dan mudah dimengerti:



Di sini Anda dapat mencatat bahwa reaksi untuk "Batal" dan "Kembali" telah kami tentukan di UserSelection. Dan reaksi terhadap tombol Batal ini berfungsi untuk semua tanpa kecuali sub-kondisi UserSelection (termasuk sub-state ServicesSelection komposit lainnya). Tetapi dalam sub-kondisi ServicesSelection, reaksi terhadap tombol Kembali sudah berbeda - pengembaliannya tidak di WelcomScreen, tetapi di ServicesScreen.

CA yang menggunakan hierarki / bersarang negara disebut hierarchical finite state Machines (ICA).

Reaksi untuk masuk / keluar ke / dari negara


Fitur yang sangat berguna adalah kemampuan untuk menetapkan respons untuk memasuki keadaan tertentu, serta reaksi untuk keluar dari keadaan. Jadi, dalam contoh di atas dengan infokiosk, pawang dapat digantung untuk memasuki setiap negara bagian, yang akan mengubah isi layar infokiosk.

Contoh sebelumnya dapat diperluas sedikit. Misalkan kita memiliki dua sub-kondisi di WelcomScreen: BrightWelcomScreen, di mana layar akan disorot secara normal, dan DarkWelcomScreen, di mana kecerahan layar akan berkurang. Kita bisa membuat entri handler DarkWelcomScreen yang akan meredupkan layar. Dan penangan keluar DarkWelcomScreen yang akan mengembalikan kecerahan normal.



Perubahan status secara otomatis setelah waktu yang ditentukan


Kadang-kadang, mungkin perlu untuk membatasi tinggal pesawat ruang angkasa dalam keadaan tertentu. Jadi, dalam contoh di atas, kita dapat membatasi waktu ICA kita tetap dalam status BrightWelcomScreen menjadi satu menit. Begitu menit kedaluwarsa, ICA secara otomatis beralih ke status DarkWelcomScreen.

Sejarah pesawat ruang angkasa


Fitur lain yang sangat berguna dari ICA adalah sejarah keadaan pesawat ruang angkasa.

Mari kita bayangkan bahwa kita memiliki semacam ICA abstrak dari jenis ini:



ICA kami ini dapat beralih dari TopLevelState1 ke TopLevelState2 dan sebaliknya. Tetapi di dalam TopLevelState1 ada beberapa negara bagian bersarang. Jika ICA hanya berpindah dari TopLevelState2 ke TopLevelState1, maka dua status segera diaktifkan: TopLevelState1 dan NestedState1. NestedState1 diaktifkan karena merupakan substrat awal dari status TopLevelState1.

Sekarang bayangkan bahwa lebih lanjut ICA kami mengubah statusnya dari NestedState1 ke NestedState2. Di dalam NestedState2, SubState InternalState1 diaktifkan (karena ini adalah substrat awal untuk NestedState2). Dan dari InternalState1 kami pergi ke InternalState2. Dengan demikian, kita secara simultan memiliki status berikut aktif: TopLevelState1, NestedState2 dan InternalState2. Dan di sini kita pergi ke TopLevelState2 (mis. Kita umumnya meninggalkan TopLevelState1).

Aktif menjadi TopLevelState2. Setelah itu kami ingin kembali ke TopLevelState1. Itu ada di TopLevelState1, dan tidak di substate tertentu di TopLevelState1.

Jadi, dari TopLevelState2 kita pergi ke TopLevelState1 dan ke mana kita bisa?

Jika TopLevelState1 tidak memiliki riwayat, maka kita akan datang ke TopLevelState1 dan NestedState1 (karena NestedState1 adalah media awal untuk TopLevelState1). Yaitu seluruh cerita tentang transisi di dalam TopLevelState1, yang terjadi sebelum meninggalkan TopLevelState2, benar-benar hilang.

Jika TopLevelState1 memiliki yang disebut sejarah dangkal, maka ketika kembali dari TopLevelState2 ke TopLevelState1 kita masuk ke NestedState2 dan InternalState1. Kami masuk ke NestedState2 karena dicatat dalam riwayat status TopLevelState1. Dan kita masuk ke InternalState1 karena ini adalah yang pertama untuk NestedState2. Ternyata dalam sejarah dangkal untuk TopLevelState1, informasi disimpan hanya tentang substrat tingkat pertama. Sejarah status tersemat di substrat ini tidak dipertahankan.

Tetapi jika TopLevelState1 memiliki sejarah yang mendalam, maka ketika kita kembali dari TopLevelState2 ke TopLevelState1 kita masuk ke NestedState2 dan InternalState2. Karena dalam riwayat yang mendalam, informasi lengkap tentang media aktif disimpan, terlepas dari dalamnya.

Keadaan ortogonal


Sejauh ini, kami telah memeriksa ICA di mana hanya satu substate yang bisa aktif di dalam negara. Tetapi kadang-kadang mungkin ada situasi ketika dalam keadaan tertentu ICA harus ada beberapa substrat aktif secara bersamaan. Substrat semacam itu disebut keadaan ortogonal.

Contoh klasik yang menunjukkan status orthogonal adalah keyboard komputer yang sudah dikenal dan mode NumLock, CapsLock dan ScrollLock. Kita dapat mengatakan bahwa bekerja dengan NumLock / CapsLock / ScrollLock dijelaskan oleh substrat ortogonal di dalam status aktif:



Segala sesuatu yang Anda ingin tahu tentang mesin negara yang terbatas, tapi ...


Secara umum, ada artikel mendasar tentang notasi formal untuk diagram keadaan dari David Harel: Statecharts: A Formalism Visual Untuk Sistem Kompleks (1987) .

Di sana, berbagai situasi yang dapat ditemui ketika bekerja dengan mesin negara hingga diperiksa menggunakan contoh mengendalikan jam elektronik biasa. Jika seseorang belum membacanya, saya sangat merekomendasikannya. Pada dasarnya, semua yang dijelaskan Harel kemudian masuk ke notasi UML. Tetapi ketika Anda membaca deskripsi diagram keadaan dari UML, Anda tidak selalu mengerti apa, mengapa, dan kapan Anda membutuhkannya. Namun dalam artikel oleh Harel, presentasi beralih dari situasi sederhana ke situasi yang lebih kompleks. Dan Anda lebih sadar semua kekuatan yang menyembunyikan mesin negara terbatas dalam diri mereka sendiri.

Mesin keadaan terbatas di SObjectizer


Selanjutnya kita akan berbicara tentang SObjectizer dan spesifiknya. Jika Anda tidak cukup memahami contoh di bawah ini, mungkin masuk akal untuk mempelajari lebih lanjut tentang SObjectizer. Misalnya, dari artikel ulasan kami tentang SObjecizer dan beberapa yang berikutnya yang memperkenalkan pembaca ke SObjectizer, bergerak dari yang sederhana ke kompleks (artikel pertama , kedua dan ketiga ).

Agen di SObjectizer adalah mesin negara


Agen di SObjectizer dari awal adalah mesin negara dengan status eksplisit. Bahkan jika pengembang agen tidak mendeskripsikan salah satu dari statusnya sendiri di kelas agennya, agen masih memiliki status default, yang digunakan secara default. Misalnya, jika pengembang membuat agen sepele seperti itu:
class simple_demo final : public so_5::agent_t { public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event<how_are_you>([]{ std::cout << "I'm fine!" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); } }; 

maka dia mungkin bahkan tidak curiga bahwa pada kenyataannya semua langganan yang dibuatnya dibuat untuk keadaan default. Tetapi jika pengembang menambahkan statusnya sendiri ke agen, maka Anda harus memikirkan untuk menandatangani agen dengan benar dalam kondisi yang benar. Di sini, katakanlah, modifikasi agen yang sederhana (dan, seperti biasa) yang ditunjukkan di atas:
 class simple_demo final : public so_5::agent_t { // ,  ,   . state_t st_free{this}; // ,  ,   . state_t st_busy{this}; public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { so_subscribe_self() .event<quit>([this]{ so_deregister_agent_coop_normally(); }); //   how_are_you   ,    . st_free.event([]{ std::cout << "I'm free" << std::endl; }); st_busy.event([]{ std::cout << "I'm busy" << std::endl; }); //     st_free. this >>= st_free; } }; 

Kami menetapkan dua penangan yang berbeda untuk sinyal how_are_you, masing-masing untuk kondisi masing-masing.

Dan kesalahan dalam modifikasi agen simple_demo ini adalah berada di st_free atau st_busy agen tidak akan merespon untuk berhenti sama sekali, karena kami meninggalkan langganan berhenti dalam keadaan default, tetapi tidak membuat langganan yang sesuai untuk st_free dan st_busy. Cara sederhana dan jelas untuk memperbaiki masalah ini adalah dengan menambahkan langganan yang sesuai ke st_free dan st_busy:
  simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { //   how_are_you   ,    . st_free .event([]{ std::cout << "I'm free" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); st_busy .event([]{ std::cout << "I'm busy" << std::endl; }) .event<quit>([this]{ so_deregister_agent_coop_normally(); }); //     st_free. this >>= st_free; } 

Benar, metode ini berbau copy-paste, yang tidak bagus. Anda dapat menghilangkan salin-tempel dengan memasukkan status induk umum untuk st_free dan st_busy:
 class simple_demo final : public so_5::agent_t { //      . state_t st_basic{this}; // ,  ,   . //      st_basic. state_t st_free{initial_substate_of{st_basic}}; // ,  ,   . //     st_basic. state_t st_busy{substate_of{st_basic}}; public: //   ,       . struct how_are_you final : public so_5::signal_t {}; //   ,     . struct quit final : public so_5::signal_t {}; // ..   ,      . simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} { //   quit   st_basic    //  ""  . st_basic.event<quit>([this]{ so_deregister_agent_coop_normally(); }); //   how_are_you   ,    . st_free.event([]{ std::cout << "I'm free" << std::endl; }); st_busy.event([]{ std::cout << "I'm busy" << std::endl; }); //     st_free. this >>= st_free; } }; 

Demi keadilan, harus ditambahkan bahwa awalnya agen SObjectizer hanya bisa menjadi mesin negara sederhana. Dukungan untuk pesawat ruang angkasa hirarkis muncul relatif baru, pada Januari 2016.

Mengapa agen SObjectizer mesin negara terbatas?


Pertanyaan ini memiliki jawaban yang sangat sederhana: Kebetulan bahwa akar SObjectizer tumbuh dari dunia sistem kontrol proses, dan di sana mesin negara terbatas sangat sering digunakan. Oleh karena itu, kami menganggap perlu bahwa agen di SObjectizer juga menjadi mesin negara. Ini sangat nyaman jika dalam aplikasi yang SObjectizer mereka coba terapkan, CA digunakan. Dan keadaan default, yang dimiliki semua agen, memungkinkan kita untuk tidak memikirkan pesawat ruang angkasa jika penggunaan pesawat ruang angkasa tidak diperlukan.

Pada prinsipnya, jika Anda melihat Model Aktor itu sendiri, dan pada prinsip-prinsip di mana model ini dibangun:

  • seorang aktor adalah entitas dengan perilaku;
  • aktor merespons pesan yang masuk;
  • Setelah menerima pesan, aktor dapat:
    • mengirim sejumlah pesan ke aktor lain;
    • membuat sejumlah aktor baru;
    • Tetapkan perilaku baru untuk memproses pesan berikutnya.

Seseorang dapat menemukan kesamaan yang kuat antara pesawat ruang angkasa sederhana dan aktor. Anda bahkan bisa mengatakan bahwa aktor adalah mesin negara terbatas sederhana.

Fitur apa dari mesin canggih yang didukung SObjectizer?


Dari fitur-fitur di atas dari mesin keadaan terbatas tingkat lanjut, SObjectizer mendukung semuanya kecuali keadaan ortogonal. Barang lain, seperti status bersarang, penangan input / output, pembatasan waktu yang dihabiskan di negara bagian, riwayat untuk negara bagian, didukung.

Dengan dukungan negara ortogonal, pertama kalinya tidak tumbuh bersama. Di satu sisi, arsitektur internal SObjectizer tidak dimaksudkan untuk mendukung beberapa keadaan agen yang independen dan aktif secara bersamaan. Di sisi lain, ada pertanyaan ideologis tentang bagaimana agen yang memiliki keadaan ortogonal harus berperilaku. Kusut pertanyaan-pertanyaan ini ternyata terlalu rumit, dan knalpot yang berguna terlalu kecil untuk menyelesaikan masalah ini. Ya, dan dalam praktik kami, belum ada situasi di mana keadaan ortogonal diperlukan, tetapi tidak mungkin dilakukan, misalnya, dengan membagi pekerjaan di antara beberapa agen yang terkait dengan satu konteks kerja yang sama.

Namun, jika seseorang membutuhkan fitur seperti status orthogonal, dan Anda memiliki contoh tugas dunia nyata di mana ini sangat diminati, maka mari kita bicara. Mungkin, dengan memberikan contoh nyata di depan mata kita, kita dapat menambahkan fitur ini ke SObjectizer.

Bagaimana dukungan untuk fitur-fitur canggih ICA terlihat dalam kode


Pada bagian cerita ini, kami akan mencoba untuk dengan cepat membahas API SObjectizer-5 untuk bekerja dengan ICA. Tanpa merinci lebih dalam, hanya agar pembaca memiliki gagasan tentang apa itu dan bagaimana tampilannya. Informasi lebih rinci, jika diinginkan, dapat ditemukan di dokumentasi resmi .

Amerika Serikat


Untuk mendeklarasikan status bersarang, Anda harus meneruskan ekspresi initial_substate_of atau substate_of ke konstruktor objek state_t yang sesuai:
 class demo : public so_5::agent_t { state_t st_parent{this}; //  . state_t st_first_child{initial_substate_of{st_parent}}; //   . //    . state_t st_second_child{substate_of{st_parent}}; //   . state_t st_third_child{substate_of{st_parent}}; //   . state_t st_first_grandchild{initial_substate_of{st_third_child}}; //    . state_t st_second_grandchild{substate_of{st_third_child]}; ... }; 

Jika negara S memiliki beberapa substrat C1, C2, ..., Cn, maka salah satu dari mereka (dan hanya satu) harus ditandai sebagai initial_substate_of. Pelanggaran aturan ini didiagnosis pada saat run-time.

Kedalaman maksimum keadaan sarang di SObjectizer-5 terbatas. Dalam versi 5.5, ini adalah 16 level. Pelanggaran aturan ini didiagnosis pada saat run-time.

Trik paling penting dengan status bersarang adalah ketika kondisi yang memiliki status bersarang diaktifkan, maka beberapa negara diaktifkan sekaligus. Misalkan ada keadaan A yang memiliki substrat B dan C, dan dalam substate B ada substrat D dan E:



Ketika negara A diaktifkan, maka, pada kenyataannya, tiga negara diaktifkan segera: A, AB, dan ABD

Fakta bahwa beberapa negara dapat aktif sekaligus memiliki efek paling serius pada dua hal kearsipan. Pertama, untuk mencari pawang untuk pesan masuk berikutnya. Jadi, dalam contoh yang baru saja ditampilkan, penangan pesan akan pertama-tama dicari dalam keadaan ABD. Jika tidak ada penangan yang cocok di sana, pencarian akan dilanjutkan dalam keadaan induknya, yaitu. di AB Dan sudah sakit, jika perlu, pencarian akan terus dalam status A.

Kedua, keberadaan beberapa status aktif mempengaruhi urutan pemanggilan penangan input / output untuk negara. Tetapi ini akan dibahas di bawah.

Penangan I / O Negara


Untuk keadaan, masuk dan keluar negara penangan negara dapat ditentukan. Ini dilakukan dengan menggunakan metode state_t :: on_enter dan state_t :: on_exit. Biasanya, metode ini disebut dalam metode so_define_agent () (atau langsung di konstruktor agen jika agen itu sepele dan warisan dari itu tidak disediakan).
 class demo : public so_5::agent_t { state_t st_free{this}; state_t st_busy{this}; ... void so_define_agent() override { // :       , //     . st_free.on_enter([]{ ... }); st_busy.on_exit([]{ ...}); ... this >>= st_free; } ... }; 

Mungkin saat yang paling sulit dengan penangan on_enter / on_exit menggunakan mereka untuk status bersarang. Mari kita kembali ke contoh dengan status A, B, C, D, dan E.



Misalkan setiap negara memiliki penangan on_enter dan on_exit.

Biarkan A. menjadi kondisi agen saat ini. status A, AB dan ABD diaktifkan Selama perubahan status agen, A.on_enter, ABon_enter, dan ABDon_enter akan dipanggil. Dan dalam urutan itu.

Misalkan ada transisi ke ABE. ABDon_exit dan ABEon_enter akan dipanggil.

Jika kemudian kita menempatkan agen dalam kondisi AC, maka ABEon_exit, ABon_exit, ACon_enter akan dipanggil.

Jika agen, sedang dalam kondisi AC, dideregistrasi, maka segera setelah penyelesaian metode so_evt_finish (), penangan ACon_exit dan A.on_exit akan dipanggil.

Batas waktu


Batas waktu untuk agen untuk tetap dalam keadaan tertentu diatur menggunakan metode state_t :: time_limit. Seperti pada on_enter / on_exit, metode time_limit biasanya dipanggil di mana agen dikonfigurasi untuk bekerja di dalam SObjectizer:
 class led_indicator : public so_5::agent_t { state_t inactive{this}; state_t active{this}; ... void so_define_agent() override { //        15s. //        inactive. active.time_limit(15s, inactive); ... } ... }; 

Jika batas waktu untuk keadaan diatur, maka segera setelah agen memasuki keadaan ini, SObjectizer mulai menghitung waktu yang dihabiskan di negara. Jika agen meninggalkan negara, dan kemudian kembali ke negara ini lagi, maka hitung mundur dimulai lagi.

Jika batas waktu ditetapkan untuk status tertanam, maka Anda harus berhati-hati, karena mungkin ada trik-trik aneh:
 class demo : public so_5::agent_t { //   . state_t A{this}, B{this}; //   first . state_t C{initial_substate_of{A}}, st_D{substate_of{A}}; ... void so_define_agent() override { A.time_limit(15s, B); C.time_limit(10s, D); D.time_limit(20s, C); ... } ... }; 

Misalkan agen memasuki negara A. I.e. negara A dan C diaktifkan untuk A dan C. Sebelumnya, ini akan berakhir untuk negara C dan agen akan beralih ke negara D. Ini akan memulai penghitungan mundur untuk tetap di negara D. Tetapi penghitungan mundur akan terus untuk tinggal di A! Karena selama transisi dari C ke D, agen terus tetap dalam keadaan A. Dan lima detik setelah transisi paksa dari C ke D, agen akan pergi ke keadaan B.

Kisah demi keberuntungan


Secara default, status agen tidak memiliki riwayat. Untuk mengaktifkan penyimpanan riwayat untuk suatu negara, berikan konstanta shallow_history (negara akan memiliki sejarah dangkal) atau deep_history (negara akan memiliki sejarah yang mendalam) ke konstruktor state_t. Sebagai contoh:
 class demo : public so_5::agent_t { state_t A{this, shallow_history}; state_t B{this, deep_history}; ... }; 

Sejarah negara adalah topik yang sulit, terutama ketika kedalaman negara bagian yang layak digunakan dan substrat memiliki sejarahnya sendiri. Karena itu, untuk informasi yang lebih lengkap tentang topik ini, lebih baik merujuk ke dokumentasi , untuk bereksperimen. Nah, untuk bertanya kepada kami apakah Anda tidak bisa mengetahuinya sendiri;)

just_switch_to, transfer_to_state, tekan


Kelas state_t memiliki sejumlah metode yang paling umum digunakan yang telah ditunjukkan di atas: event () untuk berlangganan acara ke pesan, on_enter () dan on_exit () untuk mengatur input / output handler, time_limit () untuk menetapkan batas waktu yang dihabiskan di suatu negara.

Bersamaan dengan metode ini, ketika bekerja dengan ICA, metode berikut dari kelas state_t sangat berguna:

Metode just_switch_to (), yang dirancang untuk kasus ketika satu-satunya reaksi terhadap pesan masuk adalah mentransfer agen ke keadaan baru. Anda dapat menulis:
 some_state.just_switch_to<some_msg>(another_state); 

bukannya:
 some_state.event([this](mhood_t<some_msg>) { this >>= another_state; }); 

Metode transfer_to_state () sangat berguna ketika kita memiliki beberapa pesan M diproses dengan cara yang sama di dua negara bagian S1, S2, ..., Sn. Tetapi, jika kita berada di negara S2, ..., Sn, maka pertama kita harus kembali ke S1, dan baru kemudian melakukan pemrosesan M.

Jika ini terdengar rumit, maka mungkin dalam contoh kode situasi ini akan lebih dipahami:
 class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ... //           S1, //      M  . S2.event([this](mhood_t<M> cmd) { this >>= S1; actual_M_handler(cmd); }); ... //      . Sn.event([this](mhood_t<M> cmd) { this >>= S1; actual_M_handler(cmd); }); } ... }; 

Tapi alih-alih mendefinisikan event handler yang sangat mirip untuk S2, ..., Sn, gunakan transfer_to_state:
 class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ... //           S1, //      M  . S2.transfer_to_state<M>(S1); ... //      . Sn.transfer_to_state<M>(Sn); } ... }; 

Metode suppress () menekan pencarian event handler untuk substate saat ini dan semua substrat induknya. Misalkan kita memiliki status induk A di mana std :: abort () dipanggil pada pesan M. Dan ada status anak B di mana M dapat diabaikan dengan aman. Kita harus menentukan reaksi terhadap M dalam substate B, karena jika kita tidak melakukannya, maka pawang untuk B akan ditemukan dalam A. Oleh karena itu, kita perlu menulis sesuatu seperti:
 void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.event([](mhood_t<M>) {}); //    ,      //   M   . ... } 

Metode suppress () memungkinkan Anda untuk menulis situasi ini dalam kode secara lebih eksplisit dan grafis:
 void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.suppress<M>(); //    ,      //   M   . ... } 

Contoh yang sangat sederhana


Contoh standar SObjectizer v.5.5 termasuk contoh sederhana, blinking_led , yang mensimulasikan pengoperasian indikator LED yang berkedip. Diagram keadaan agen dari contoh ini adalah sebagai berikut:



Dan di sini adalah kode agen lengkap dari contoh ini:
 class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } }; 

Di sini, semua pekerjaan aktual dilakukan di dalam penangan I / O untuk sub-negara blink_on. Nah, ditambah, batas durasi tinggal di media blink_on dan blink_off berfungsi.

Bukan contoh yang sangat sederhana


Contoh standar SObjectizer v.5.5 juga mencakup contoh yang jauh lebih kompleks, intercom_statechart , yang meniru perilaku panel doorphone. Dan diagram keadaan agen utama dalam contoh ini terlihat seperti ini:



Semuanya begitu keras karena peniruan ini mendukung tidak hanya memanggil apartemen dengan nomor, tetapi juga hal-hal seperti kode rahasia yang unik untuk setiap apartemen, serta kode layanan khusus. Kode-kode ini memungkinkan Anda untuk membuka kunci pintu tanpa menekan nomor di mana pun.

Masih ada hal-hal menarik dalam contoh ini. Tetapi terlalu besar untuk dijelaskan secara rinci (bahkan artikel terpisah mungkin tidak cukup untuk ini). Jadi jika Anda tertarik pada bagaimana ICA terlihat sangat rumit di SObjectizer, Anda dapat melihat dalam contoh ini. Dan jika ada sesuatu yang tidak jelas, maka Anda dapat mengajukan pertanyaan kepada kami. Misalnya dalam komentar di artikel ini.

Apakah mungkin untuk tidak menggunakan dukungan untuk pesawat ruang angkasa yang dibangun ke dalam SObjectizer-5?


Jadi, SObjectizer-5 memiliki dukungan bawaan untuk ICA dengan beragam fitur yang didukung. Dukungan ini dibuat, tentu saja, untuk menggunakannya. Secara khusus, mekanisme debug SObjectizer, seperti pelacakan pengiriman pesan , mengetahui keadaan agen dan menampilkan keadaan saat ini dalam pesan debug masing-masing.

Namun demikian, jika pengembang tidak ingin karena alasan tertentu menggunakan alat SObjectizer-5 bawaan, maka ia mungkin tidak melakukan ini.

Misalnya, Anda dapat menolak untuk menggunakan SObjectizer state_t dan yang lain menyukainya karena state_t adalah objek yang cukup berat dengan di dalam std :: string, beberapa std :: function, beberapa penghitung seperti std :: size_t, lima petunjuk ke berbagai objek dan beberapa lainnya. Bersama-sama, ini pada Linux 64-bit dan GCC-5.5, misalnya, memberikan 160 byte per state_t (terlepas dari apa yang dapat dialokasikan dalam memori dinamis).

Jika Anda memerlukan, katakanlah, sejuta agen dalam aplikasi, yang masing-masing akan memiliki 10 negara, maka overhead SObjectizer state_t mungkin tidak dapat diterima. Dalam hal ini, Anda dapat menggunakan beberapa mekanisme lain untuk bekerja dengan mesin negara, mendelegasikan pemrosesan pesan secara manual ke mekanisme ini. Sesuatu seperti:
 class external_fsm_demo : public so_5::agent_t { some_fsm_type my_fsm_; ... void so_define_agent() override { so_subscribe_self() .event([this](mhood_t<msg_one> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_two> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_three> cmd) { my_fsm_.handle(*cmd); }); ... } ... }; 

Dalam hal ini, Anda membayar untuk efisiensi dengan meningkatkan jumlah pekerjaan manual dan kurangnya bantuan dari mekanisme debugging SObjectizer. Tapi di sini terserah pengembang untuk memutuskan.

Kesimpulan


Artikel itu ternyata sangat banyak, lebih dari yang direncanakan. Terima kasih untuk semua orang yang membaca tempat ini. Jika salah satu pembaca menganggap mungkin untuk meninggalkan umpan balik Anda di komentar ke artikel, maka itu akan sangat bagus.

Jika sesuatu tetap tidak jelas, maka ajukan pertanyaan, kami akan menjawab dengan senang hati.

Juga, dengan mengambil kesempatan ini, saya ingin menarik perhatian mereka yang tertarik pada SObjectizer, bahwa pekerjaan telah dimulai pada versi berikutnya dari SObjectizer dalam kerangka cabang 5.5. Secara singkat tentang apa yang dipertimbangkan untuk implementasi di 5.5.23, dijelaskan di sini . Lebih lengkap, tetapi dalam bahasa Inggris, di sini . Anda dapat meninggalkan pendapat Anda tentang salah satu fitur yang diusulkan untuk implementasi, atau menawarkan sesuatu yang lain. Yaituada peluang nyata untuk mempengaruhi pengembangan SObjectizer. Selain itu, setelah rilis v.5.5.23, mungkin ada jeda dalam pekerjaan pada SObjectizer dan kesempatan berikutnya untuk memasukkan sesuatu yang berguna dalam 201b SObjectizer mungkin tidak mungkin.

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


All Articles