SObjectizer-5.5の4年間の開発。 この間にSObjectizerはどのように変更されましたか?

5.5ブランチ内のSObjectizerの最初のバージョンは、4年以上前-2014年10月上旬にリリースされました。 そして今日、次のバージョンが5.5.23という番号でリリースされました 。これは、おそらくSObjectizer-5.5の歴史を終わらせるでしょう。 私の意見では、これは過去4年間に何が行われたかを振り返って見る絶好の機会です。



この記事では、最も重要で重要な変更とイノベーションを抽象的に分析しようとします:追加されたもの、理由、SObjectizer自体またはその使用にどのように影響したか。



おそらく誰かが考古学の観点からそのような物語に興味を持つでしょう。 そしておそらく、誰かが、C ++用の独自のアクターフレームワークの開発のような疑わしい冒険を阻止されるでしょう。



古いC ++コンパイラの役割に関する少し叙情的な余談



SObjectizer-5の歴史は2010年半ばに始まりました。 同時に、すぐにC ++ 0xに注目しました。 すでに2011年に、SObjectizer-5の最初のバージョンが製品コードの記述に使用され始めました。 そのとき、通常のC ++ 11をサポートするコンパイラがなかったことは明らかです。



長い間、「modern C ++」のすべての機能を完全に使用することはできませんでした:可変長テンプレート、noexcept、constexprなど。これはSObjectizer APIに影響を与えるだけでした。 そして、それは非常に長い間影響を及ぼしました。 したがって、機能の説明を読むときに「なぜ以前に行われなかったのか」という質問がある場合、この質問に対する答えは「以前は不可能だったから」である可能性が最も高いです。



過去にSObjectizer-5.5で何が表示および/または変更されましたか?



このセクションでは、SObjectizerに大きな影響を与えた多くの機能について説明します。 このリストの順序はランダムであり、説明されている機能の「重要性」や「重み」とは関係ありません。



so_5 :: rt名前空間の拒否



どうした?



最初に、5番目のSObjectizerでは、SObjectizerランタイムに関連するすべてがso_5 :: rt名前空間内で定義されました。 たとえば、so_5 :: rt :: environment_t、so_5 :: rt :: agent_t、so_5 :: rt :: message_tなどがありました。 たとえば、SO-5.5.0の従来のHelloWorldの例で見ることができるもの:



#include <so_5/all.hpp> class a_hello_t : public so_5::rt::agent_t { public: a_hello_t( so_5::rt::environment_t & env ) : so_5::rt::agent_t( env ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5." << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::rt::environment_t & env ) { env.register_agent_as_coop( "coop", new a_hello_t( env ) ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; }
      
      





略語「rt」はランタイムを表します。 そして、レコード "so_5 :: rt"は、 "so_5 ::ランタイム"よりもはるかに優れており、実用的であるように思われました。



しかし、多くの人にとって「rt」は「リアルタイム」に過ぎず、他には何もないことが判明しました。 また、「ランタイム」の略語として「rt」を使用すると、感情が大きく損なわれるため、RuNetでのSObjectizerのバージョンのアナウンスが、「リアルタイム」以外の「rt」の許可されない解釈をテーマにホリバーに変わることがあります。



結局、私たちはそれにうんざりしています。 そして、so_5 :: rt名前空間の準備を解除しました。



何になったの?



「so_5 :: rt」内で定義されたすべてのものは、単に「so_5」に切り替わりました。 その結果、同じHelloWorldは次のようになります。



 #include <so_5/all.hpp> class a_hello_t : public so_5::agent_t { public: a_hello_t( context_t ctx ) : so_5::agent_t( ctx ) {} void so_evt_start() override { std::cout << "Hello, world! This is SObjectizer v.5 (" << SO_5_VERSION << ")" << std::endl; so_environment().stop(); } void so_evt_finish() override { std::cout << "Bye! This was SObjectizer v.5." << std::endl; } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { env.register_agent_as_coop( "coop", env.make_agent<a_hello_t>() ); } ); } catch( const std::exception & ex ) { std::cerr << "Error: " << ex.what() << std::endl; return 1; } return 0; }
      
      





しかし、「so_5 :: rt」の古い名前は、通常のs(typedefs)の使用を通じて、とにかく利用可能のままでした。 したがって、SO-5.5の最初のバージョン用に記述されたコードは、SO-5.5の最近のバージョンでも機能します。



最後に、so_5 :: rt名前空間はバージョン5.6で削除されます。



どのような影響がありましたか?



おそらく、SObjectizerのコードは読みやすくなりました。 それでも、so_5 :: send()は、so_5 :: rt :: send()よりも適切に認識されます。



さて、ここでは、SObjectizer開発者と同様に、頭痛の種が減りました。 一度にSObjectizerのアナウンスの周りに空っぽのチャットと不必要な推論が多すぎました(「なぜC ++のアクターが必要なのか」という質問から始まり、「エンティティに名前を付けるためにPascalCaseを使用しないのはなぜか」で終わる)。 1つの可燃性のテーマが少なくなり、良かった:)



簡素化されたメッセージ送信とメッセージハンドラーの進化



どうした?



SObjectizer-5.5の最初のバージョンでも、通常のメッセージはdeliver_messageメソッドを使用して送信されましたが、これは受信者のmboxで呼び出す必要がありました。 保留中または定期的なメッセージを送信するには、environment_t型のオブジェクトでsingle_timer / schedule_timerを呼び出す必要がありました。 また、別のエージェントにすでに同期リクエストを送信するには、通常、一連の操作全体が必要でした。 ここで、たとえば、4年前にどのように見えたか(std :: make_unique()はC ++ 11ではまだ利用できませんでしたが、すでに使用されています):



 //   . mbox->deliver_message(std::make_unique<my_message>(...)); //   . env.single_timer(std::make_unique<my_message>(...), mbox, std::chrono::seconds(2)); //   . auto timer_id = env.schedule_timer( std::make_unique<my_message>(...), mbox, std::chrono::seconds(2), std::chrono::seconds(5)); //         10 . auto reply = mbox->get_one<std::string>() .wait_for(std::chrono::seconds(10)) .sync_get(std::make_unique<my_message>(...));
      
      





さらに、SObjectizerのメッセージハンドラーの形式はバージョン5.5に進化しました。 最初にSObjectizer-5を使用する場合、すべてのハンドラーの形式は次のとおりです。



 void evt_handler(const so_5::event_data_t<Msg> & cmd);
      
      





時間が経つにつれて、許可される形式にさらにいくつかが追加されました。



 //  ,  Msg --  ,   . ret_value evt_handler(const Msg & msg); ret_value evt_handler(Msg msg); //  ,     . ret_value evt_handler();
      
      





以来、新しいハンドラ形式が広く使用されるようになりました 常に「const so_5 :: event_data_t <Msg>&」をペイントすることは、まだ楽しみです。 しかし、一方で、より単純な形式はテンプレートエージェントにとって使いやすいものではありませんでした。 例:



 template<typename Msg_To_Process> class my_actor : public so_5::agent_t { void on_receive(const Msg_To_Process & msg) { // Oops! ... } };
      
      





このようなテンプレートエージェントは、Msg_To_Processがメッセージタイプであり、シグナルタイプではない場合にのみ機能します。



何になったの?



ブランチ5.5では、送信機能のファミリーが登場し、大幅に進化しました。 これを行うには、まず、可変長テンプレートをサポートするコンパイラーを自由に使用する必要がありました。 そして、第二に、一般的な可変引数テンプレートとsend-functionsの最初のバージョンの両方で作業する十分な経験を蓄積します。 さらに、さまざまなコンテキストで:通常のエージェント、アドホックエージェント、テンプレートクラスによって実装されるエージェント、および一般的な外部エージェント。 mchainsでsend-functionsを使用する場合を含めます(以下で説明します)。



送信関数に加えて、エージェント間の同期相互作用のために設計されたrequest_future / request_value関数が登場しました。



その結果、メッセージの送信は次のようになりました。



 //   . so_5::send<my_message>(mbox, ...); //   . so_5::send_delayed<my_message>(env, mbox, std::chrono::seconds(2), ...); //   . auto timer_id = so_5::send_periodic<my_message>( env, mbox, std::chrono::seconds(2), std::chrono::seconds(5), ...); //         10 . auto reply =so_5::request_value<std::string, my_message>(mbox, std::chrono::seconds(10), ...);
      
      





メッセージハンドラの別の可能な形式が追加されました。 さらに、SObjectizerの次のメジャーリリースでメイン(そして、おそらく唯一の)として残されるのは、この形式です。 これは次の形式です。



 ret_type evt_handler(so_5::mhood_t<Msg> cmd);
      
      





Msgは、メッセージタイプまたはシグナルタイプのいずれかです。



この形式は、通常のクラスの形式のエージェントとテンプレートクラスの形式のエージェントの間の境界を曖昧にするだけではありません。 ただし、メッセージ/シグナルの転送も簡素化されます(送信機能ファミリのおかげです)。



 void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ... // -  . //       . so_5::send(another_agent, std::move(cmd)); }
      
      





どのような影響がありましたか?



mhood_t <Msg>を受信する送信関数とメッセージハンドラの外観は、メッセージが送信および処理されるコードを根本的に変更したと言えます。 これは、SObjectizer-5の作業の最初の段階で、可変個引数テンプレートをサポートするコンパイラも、それらを使用した経験もなかったことを後悔するだけの場合です。 送信関数とmhood_tのファミリは、最初から必要でした。 しかし、歴史は発展するにつれて発展しました...



カスタムメッセージのサポート



どうした?



最初は、送信されたすべてのメッセージはso_5 :: message_tクラスの子孫クラスであると想定されていました。 例:



 struct my_message : public so_5::message_t { ... //  my_message. my_message(...) : ... {...} //   my_message. };
      
      





5番目のSObjectizerは私たちだけが使用しましたが、これは問題を引き起こしませんでした。 まあ、このように、そのように。



しかし、サードパーティのユーザーがSObjectizerに興味を持ち始めるとすぐに、「so_5 :: message_tからメッセージを継承する必要がありますか?」という定期的に繰り返される質問にすぐに遭遇しました。ユーザーがまったく影響を与えられなかったタイプ。 ユーザーがSObjectizerと他の外部ライブラリを使用するとします。 そして、この外部ライブラリには、ユーザーがメッセージとして送信したいオブジェクトである特定のタイプMがあります。 さて、どのように友人がMとso_5 :: message_tを入力するような条件で? ユーザーが手動で記述する必要があった追加のラッパーのみ。



何になったの?



メッセージタイプがso_5 :: message_tから継承されない場合でも、SObjectizer-5.5にメッセージを送信する機能を追加しました。 つまり これで、ユーザーは簡単に記述できます。



 so_5::send<std::string>(mbox, "Hello, World!");
      
      





So_5 :: message_tは、テンプレートマジックsend()が、std :: stringがso_5 :: message_tから継承されておらず、std :: stringがsend内に構築されているのではなく、so_5からの特別な継承者であるため、 :: message_t。内部には既にユーザーが必要とする文字列::文字列が入っています。



同様のテンプレートマジックがサブスクリプションに適用されます。 SObjectizerが次の形式のメッセージハンドラを見つけた場合:



 void evt_handler(mhood_t<std::string> cmd) {...}
      
      





SObjectizerは、実際には特別なメッセージにはstd :: stringオブジェクトが含まれることを理解しています。 そして、この特別なメッセージからstd :: stringへのリンクを渡してハンドラーを呼び出す必要があります。



どのような影響がありましたか?



SObjectizerの使用が簡単になりました。特に、独自のタイプのオブジェクトをメッセージとして送信するだけでなく、外部ライブラリのタイプオブジェクトも送信する必要がある場合に便利です。 何人かの人々は、この機能に特別な感謝を言うために時間を費やしました。



可変メッセージ



どうした?



当初、SObjectizer-5では、1:N相互作用モデルのみが使用されていました。 つまり 送信メッセージには複数の受信者がいる可能性があります(または複数の受信者がいる可能性があります)。 エージェントが1:1モードで対話する必要がある場合でも、マルチプロデューサー/マルチコンシューマーメールボックスを介して通信します。 つまり 1:Nモードでは、この場合のNのみが厳密に単位でした。



複数の受信者エージェントがメッセージを受信できる状況では、送信されるメッセージは不変でなければなりません。 そのため、メッセージハンドラーの形式は次のとおりです。



 //       . ret_type evt_handler(const event_data_t<Msg> & cmd); //       . ret_type evt_handler(const Msg & msg); //    . //        . ret_type evt_handler(Msg msg);
      
      





一般的に、シンプルで理解可能なアプローチ。 ただし、エージェントが1:1モードで相互に通信し、たとえば一部のデータの所有権を相互に転送する必要がある場合は、あまり便利ではありません。 すべてのメッセージが厳密に不変のオブジェクトである場合、このような単純なメッセージは実行できません。



 struct process_image : public so_5::message_t { std::unique_ptr<gif_image> image_; process_image(std::unique_ptr<gif_image> image) : image_{std::move(image)) {} };
      
      





より正確には、そのようなメッセージを送信できます。 しかし、それを定数オブジェクトとして受け取った場合、process_image :: image_の内容をそれ自体から削除することはできませんでした。 このような属性を変更可能としてマークする必要があります。 しかし、process_imageが何らかの理由で1:Nモードで送信された場合、コンパイラー側の制御を失います。



何になったの?



SObjectizer-5.5では、可変メッセージを送受信する機能が追加されました。 この場合、ユーザーは、送信時と購読時にメッセージに特別なマークを付ける必要があります。



例:



 //    . so_5::send<my_message>(mbox, ...); //     my_message. so_5::send<so_5::mutable_msg<my_message>>(mbox, ...); ... //     . void my_agent::on_some_event(mhood_t<my_message> cmd) {...} //      my_message. void my_agent::on_another_event(mhood_t<so_5::mutable_msg<my_message>> cmd) {...}
      
      





SObjectizerの場合、my_messageとmutable_msg <my_message>は2種類のメッセージです。



送信機能は、変更可能なメッセージを送信するよう要求されていることを確認すると、送信機能がチェックし、どのメールボックスにこのメッセージを送信しようとします。 マルチコンシューマボックスの場合、送信は実行されませんが、対応するエラーコードと共に例外がスローされます。 つまり SObjectizerは、1:1モードで対話する場合にのみ、単一メッセージメールボックスまたは単一チェーンメールボックスの形式であるmchainsを使用して、可変メッセージを使用できるようにします。 ところで、この保証を確実にするために、SObjectizerは、定期的なメッセージの形式で変更可能なメッセージを送信することを禁止しています。



どのような影響がありましたか?



可変メッセージでは、予想外に判明しました。 C ++ Russia-2017でのSObjectizerに関するレポートの副次的な議論の結果として、それらをSObjectizerに追加しました。 「まあ、尋ねたら誰かがそれを必要とするので、試してみる価値はあります。」 まあ、彼らは広範な需要の多くの希望なしでやった。 このため、互換性を損なうことなくSO-5.5に可変メッセージを追加する方法を考える前に、非常に長い間「竹を吸う」必要がありました。



しかし、SObjectizerで変更可能なメッセージが表示されたとき、それらのアプリケーションはそれほど多くないことが判明しました。 そして、その可変メッセージは驚くほど頻繁に使用されます(これについての言及は、Shrimpデモプロジェクトに関するストーリーの2番目の部分にあります )。 したがって、実際には、この機能は非常に便利でした。 SObjectizerレベルで変更可能なメッセージのサポートがなければ、通常の解決策がなかった問題を解決できます。



階層状態マシンエージェント



どうした?



SObjectizerのエージェントはもともとステートマシンでした。 エージェントは、状態を明示的に記述し、特定の状態のメッセージをサブスクライブする必要がありました。

例:



 class worker : public so_5::agent_t { state_t st_free{this, "free"}; state_t st_bufy{this, "busy"}; ... void so_define_agent() override { //     st_free. so_subscribe(mbox).in(st_free).event(...); //     st_busy. so_subscribe(mbox).in(st_busy).event(...); ... } };
      
      





しかし、これらは単純な状態マシンでした。 状態を互いに入れ子にすることはできませんでした。 状態の開始および終了ハンドラーはサポートされていません。 州で過ごした時間に制限はありませんでした。



ステートマシンに対するこのような限定的なサポートでさえ便利であり、1年以上使用しました。 しかし、ある時点で、もっと欲しかった。



何になったの?



SObjectizerは、階層状態マシンのサポートを導入します。



これで、ステートを相互にネストできます。 親状態のイベントハンドラーは、子状態によって自動的に「継承」されます。



状態の入り口と出口のハンドラーがサポートされています。



エージェントが状態を維持する時間に制限を設定することができます。



状態の履歴を保持することが可能です。



根拠がないように、複雑な階層状態マシンではないエージェントの例を次に示します(標準的な例のコードはblinking_ledです)。



 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 final : 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 ); } };
      
      





これについてはすでに別の記事で説明していますが、繰り返す必要はありません。



現在、直交状態のサポートはありません。 しかし、この事実には2つの説明があります。 まず、私たちはこのサポートをしようとしましたが、多くの困難に直面しましたが、それを克服することはコストがかかりすぎるように思われました。 そして、第二に、誰もまだ直交状態を要求していません。 尋ねられたら、このトピックに戻ります。



どのような影響がありましたか?



それは非常に深刻であるという感覚があります(もちろん、主観的で偏見があります)。 結局のところ、サブジェクト領域で複雑な有限状態マシンに直面したとき、回避策を探し始め、何かを単純化し、何かに余分な力を費やすのは、一つのことです。 また、アプリケーションからオブジェクトをC ++コードにほぼ1対1でマッピングできる場合は、まったく別の問題です。



さらに、たとえば、状態への/からの入力/出力ハンドラーの動作によって尋ねられる質問によって判断して、この機能が使用されます。



mchainの



どうした?



興味深い状況でした。 アプリケーションの一部のみがSObjectizerで記述されるように、SObjectizerがよく使用されました。 アプリケーションの残りのコードは、一般的にはアクターとは関係なく、特にSObjectizerとは関係ありません。 たとえば、SObjectizerがいくつかのバックグラウンドタスクに使用され、メイン作業がアプリケーションのメインスレッドで実行されるGUIアプリケーション。



そして、そのような場合、非SObjectizer部分からSObjectizer部分まで、情報の送信は単純であることがわかりました。通常の送信関数を呼び出すだけで十分です。 しかし、反対方向への情報の広がりはそれほど単純ではありません。 これは良くないようで、アプリケーションのSObjectizerパーツと非SObjectizerパーツの間に、すぐに使える便利な通信チャネルがあるはずです。



何になったの?



そのため、メッセージチェーン、またはより馴染みのある表記では、mObjectがSObjectizerに登場しました。



Mchainは、通常の送信機能によってメッセージが送信されるシングルコンシューマメールボックスの特定のバージョンです。 ただし、mchainからメッセージを抽出するために、エージェントを作成して署名する必要はありません。 エージェント内でもエージェント外でも呼び出すことができる2つの特別な関数があります:receive()およびselect()。 1つ目は1つのチャネルからのみメッセージを読み取り、2つ目は一度に複数のチャネルからメッセージを読み取ることができます。



 using namespace so_5; mchain_t ch1 = env.create_mchain(...); mchain_t ch2 = env.create_mchain(...); select( from_all().handle_n(3).empty_timeout(200ms), case_(ch1, [](mhood_t<first_message_type> msg) { ... }, [](mhood_t<second_message_type> msg) { ... }), case_(ch2, [](mhood_t<third_message_type> msg ) { ... }, [](mhood_t<some_signal_type>){...}, ... ));
      
      





すでにここで何度かmchainについて話しました: 2017年8月2018年5月 。 したがって、特にmchainsでの作業がどのように見えるかというトピックについては、ここでは詳しく説明しません。



どのような影響がありましたか?



SObjectizer-5.5でmchainsが登場した後、実際には、SObjectizerは以前よりも「アクター」フレームワークではなくなったことが判明しました。 SObjectizerは、Actor ModelとPub / Subのサポートに加えて、CSP(順次プロセスの通信)モデルのサポートも追加しました。 Mchainsを使用すると、アクターをまったく使用せずに、SObjectizerでかなり複雑なマルチスレッドアプリケーションを開発できます。 また、一部のタスクではこれは便利です。 私たち自身が時々使用するもの。



メッセージ制限メカニズム



どうした?



Model of Actorsの最も深刻な欠点の1つは、過負荷の発生の素因です。 送信側のアクターが受信側のアクターにメッセージを送信する状況は、受信側のアクターがメッセージを処理できる速度よりも速いペースで自分を見つけるのは非常に簡単です。



原則として、アクターフレームワークでメッセージを送信することは非ブロッキング操作です。 したがって、「軽快な生産者とオタク消費者」のペアが発生すると、少なくとも何らかの種類の空きメモリが存在する間、受信者のアクターのキューが増加します。



この問題の主な難点は、適用されるタスクと対象領域の特性に対して、過負荷に対する保護の優れたメカニズムを強化する必要があることです。 たとえば、どのメッセージを複製できるかを理解する(したがって、複製を安全に破棄できる)。 どのメッセージも破棄できないことを理解する。 誰がどのくらいの額で停職することができ、誰はまったく許可されません。 等



もう1つの難点は、常に優れた防御メカニズムが必要なわけではないことです。 時には、原始的であるが効果的でアクセスしやすい「箱から出して」使いやすいものがあれば十分です。 overload control , «» - .



?



, , SObjectizer-5.5 .. message limits. , , , . 例:



 class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx //     100  handle_data, //      //  . + limit_then_redirect<handle_data>(100, [this]{ return another_worker_;}) //     1  check_status, //       . + limit_then_drop<check_status>(1) //     1  reconfigure, //     , ..  reconfigure //        . + limit_then_abore<reconfigure>(1) } {...} ... };
      
      





.



?



, message limits -, SObjectizer, . , . , , .



message delivery tracing



?



SObjectizer-5 « ». … , .



, . : mbox, (, , ). , , , .



, SObjectizer Run-Time , , SObjectizer-, . , .



?



SObjectizer-5.5 , , message delivery tracing ( msg_tracing). .



, , msg_tracing , .



?



SObjectizer . .



env_infrastructure env_infrastructures



?



SObjectizer . SObjectizer-5 , .



SObjectizer- SObjectizer- . SObjectizer- ( , ).



つまり SObjectizer . .



, SObjectizer- « » , , . , , , , .



: SObjectizer - ?



?



, .



, , . .



, environment infrastructure ( env_infrastructure ). Env_infrastructure SObjectizer-. , , , .



SObjectizer- env_infrastructures. SObjectizer , , .



.



?



, , — . SObjectizer . SObjectizer . ! , SObjectizer- . .



run-time



?



SObjectizer-5 . , , , , ..



, 24/7. , / .



, , , SObjectizer .



?



SObjectizer-5.5 run-time SObjectizer- . run-time , , mbox , , , , ( , , ).



, . , - ( ).



?



run-time . , , . ( ) , [].



« », , , . なぜなら « » . . , run-time … .



-



SObjectizer-5.5 , , , . . , - .



SObjectizer-5.5 CMake.



SObjectizer-5 , .



SObjectizer-5.5 Android ( CrystaX NDK , Android NDK).



. , .



delivery filters. MPMC-mbox- , .



: introduce_coop/introduce_child_coop, make_agent/make_agent_with_binder .



lock- lock- ( mutex-, spinlock-, - ).



wrapped_env_t SObjectizer so_5::launch().



stop_guard- SObjectizer-. , SObjectizer- - .



, , (.. dead_letter_handlers).



«». - .



5.5.0 5.5.23



//. cloc SObjectizer-5.5.0:



 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C/C++ Header 58 2119 5156 5762
C++ 39 1167 779 4759
Ruby 2 30 2 75
-------------------------------------------------------------------------------
SUM: 99 3316 5937 10596
-------------------------------------------------------------------------------


, v.5.5.23 ( 1147 — optional-lite):

 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C/C++ Header 133 6279 22173 21068
C++ 53 2498 2760 10398
CMake 2 29 0 177
Ruby 4 53 2 129
-------------------------------------------------------------------------------
SUM: 192 8859 24935 31772
-------------------------------------------------------------------------------


v.5.5.0:

 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 84 2510 390 11540
Ruby 162 496 0 1054
C/C++ Header 1 11 0 32
-------------------------------------------------------------------------------
SUM: 247 3017 390 12626
-------------------------------------------------------------------------------


v.5.5.23:

 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 324 7345 1305 35231
Ruby 675 2353 0 4671
CMake 338 43 0 955
C/C++ Header 11 107 3 448
-------------------------------------------------------------------------------
SUM: 1348 9848 1308 41305


v.5.5.0:

 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 27 765 463 3322
Ruby 28 95 0 192
-------------------------------------------------------------------------------
SUM: 55 860 463 3514


, v.5.5.23:

 -------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C++ 67 2141 2061 9341
Ruby 133 451 0 868
CMake 67 93 0 595
C/C++ Header 1 12 11 32
-------------------------------------------------------------------------------
SUM: 268 2697 2072 10836


.



SObjectizer-, , .



( )



SObjectizer- 5.5.23 . . , 5.6.0, 2019- , SObjectizer-. , 2019- SObjectizer 5.6 - .



, SO-5.5 , SO-5.6 , SO-5.7.



5.7, - SO-5.5 SO-5.6, 2019- . , , 2020- .



おわりに



, SObjectizer- . , SObjectizer-. .



, SObjectizer- : . , .



- SObjectizer — . . , , .



All Articles