分支5.5框架内的SObjectizer的第一个版本发布于四年前-2014年10月上旬。 今天
,发布了下一个版本,编号为5.5.23 ,很有可能将关闭SObjectizer-5.5的历史。 我认为,这是回顾过去四年来所做的事情的重要原因。
在本文中,我将尝试抽象地分析最重要和最重要的更改和创新:添加的内容,原因,它如何影响SObjectizer本身或其使用。
从考古学的角度来看,也许有人会对这样的故事感兴趣。 也许有人会被诸如开发自己的C ++ actor框架之类的可疑冒险所困扰;)
关于旧的C ++编译器的作用有点抒情离题
SObjectizer-5的历史始于2010年中。 同时,我们立即专注于C ++ 0x。 早在2011年,SObjectizer-5的第一个版本就开始用于编写生产代码。 显然,那时我们还没有支持正常C ++ 11的编译器。
长期以来,我们无法完全使用“现代C ++”的所有功能:可变参数模板,noexcept,constexpr等。这只能影响SObjectizer API。 它影响了非常非常长的时间。 因此,如果在阅读功能描述时有一个问题“为什么以前没做过?”,则该问题的答案很可能是:“因为以前不可能做过”。
过去在SObjectizer-5.5中出现和/或改变了什么?
在本节中,我们将介绍对SObjectizer产生重大影响的许多功能。 该列表中的顺序是随机的,并且与所描述特征的“重要性”或“权重”无关。
拒绝so_5 :: rt命名空间
怎么了?
最初,在第五个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 :: runtime”更好,更实用。
但是事实证明,对于许多人来说,“ 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的最新版本中也可以使用。
最后,在5.6版中将删除so_5 :: rt名称空间。
它产生了什么影响?
大概,SObjectizer上的代码现在更具可读性。 不过,与so_5 :: rt :: send()相比,so_5 :: send()的感知更好。
好了,在这里,与SObjectizer开发人员一样,头痛也有所减轻。 一次围绕SObjectizer的公告有很多闲聊和不必要的推理(从“为什么在C ++中通常需要参与者”开始,到“为什么不使用PascalCase命名实体”这样的问题开始)。 一个易燃的话题越来越少了,这是一件好事:)
简化消息发送和消息处理程序的演变
怎么了?
即使在SObjectizer-5.5的第一个版本中,通常的消息也是使用delivery_message方法发送的,该方法必须在收件人的mbox上调用。 要发送未决消息或定期消息,必须在environment_t类型的对象上调用single_timer / schedule_timer。 已经向另一个代理发送同步请求通常需要整个操作链。 例如,这里是四年前的样子(已经使用了在C ++ 11中尚不可用的std :: make_unique()):
此外,SObjectizer中消息处理程序的格式已发展到5.5版。 如果最初使用SObjectizer-5,则所有处理程序应采用以下格式:
void evt_handler(const so_5::event_data_t<Msg> & cmd);
然后随着时间的推移,一些新的样式被添加到允许的格式中:
自此以来,新的处理程序格式已被广泛使用 不断地绘画“ 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) {
仅当Msg_To_Process是消息类型而不是信号类型时,这样的模板代理才有效。
变成了什么?
在分支5.5中,出现了一系列发送功能,并进行了重大改进。 为此,首先,我必须让编译器支持可变参数模板。 其次,要积累足够的经验,既可以使用可变参数模板,也可以使用第一版的发送功能。 而且,在不同的上下文中:在普通代理,临时代理中,在由模板类实现的代理中,以及一般在外部代理中。 包括与mchains一起使用send-function时(将在下面讨论)。
除了发送功能外,还出现了request_future / request_value函数,这些函数旨在用于代理之间的同步交互。
结果,现在发送消息如下:
已添加消息处理程序的另一种可能格式。 而且,这种格式将在SObjectizer的下一个主要版本中保留为主要格式(并且可能是唯一的一种)。 格式如下:
ret_type evt_handler(so_5::mhood_t<Msg> cmd);
Msg可以是消息类型或信号类型。
这种格式不仅模糊了普通类形式的代理与模板类形式的代理之间的界限。 但这也简化了消息/信号的转发(这要感谢send函数系列):
void my_agent::on_msg(mhood_t<Some_Msg> cmd) { ...
它产生了什么影响?
可以说,接收函数mhood_t <Msg>的发送函数和消息处理程序的出现从根本上改变了发送和处理消息的代码。 只有在遗憾的是,在SObjectizer-5的开发之初我们就没有支持可变参数模板的编译器,也没有使用它们的经验,这就是这种情况。 发送函数和mhood_t家族应该从一开始就已经存在。 但是历史随着发展而发展...
支持自定义消息类型
怎么了?
最初,所有发送的消息都应该是so_5 :: message_t类的后代类。 例如:
struct my_message : public so_5::message_t { ...
尽管第五个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 ::字符串不是从so_5 :: message_t继承的,并且在send内部没有构造一个简单的std ::字符串,而是so_5的特殊继承人:: message_t,已经在其中放置了用户所需的std ::字符串。
类似的模板魔术适用于订阅。 当SObjectizer看到以下形式的消息处理程序时:
void evt_handler(mhood_t<std::string> cmd) {...}
然后SObjectizer就会了解到,实际上其中会附带std :: string对象一个特殊消息。 而您需要通过向处理程序传递此特殊消息中指向std :: string的链接来调用处理程序。
它产生了什么影响?
使用SObjectizer变得更加容易,尤其是当您不仅需要发送自己类型的对象作为消息,而且还需要从外部库中键入对象时。 甚至有几个人花了很多时间对这个功能表示特别的感谢。
可变信息
怎么了?
最初,在SObjectizer-5中,仅使用了1:N交互模型。 即 发送的邮件可能有多个收件人(或者可能有多个)。 即使代理需要以1:1模式交互,它们仍然可以通过多生产者/多消费者邮箱进行通信。 即 在1:N模式下,在这种情况下,仅N严格来说是一个单位。
在一个消息可以被多个接收方代理接收的情况下,发送的消息必须是不可变的。 这就是消息处理程序具有以下格式的原因:
通常,一种简单易懂的方法。 但是,当座席需要以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_的内容删除到其自身。 我必须将这样的属性标记为可变的。 但是如果由于某种原因以1:N模式发送process_image,我们将失去对编译器的控制。
变成了什么?
在SObjectizer-5.5中,已添加了发送和接收可变消息的功能。 同时,用户在发送消息和订阅消息时必须特别标记消息。
例如:
对于SObjectizer,my_message和mutable_msg <my_message>是两种不同类型的消息。
当发送功能看到要求发送可变消息时,发送功能将检查以查看他们要将消息发送到哪个邮箱。 如果这是一个多用户的设备,则不会执行发送,但是会抛出异常,并带有相应的错误代码。 即 SObjectizer确保仅当以1:1模式交互时(通过单用户邮箱或单用户邮箱的mchain),才能使用可变消息。 为了提供这种保证,顺便说一句,SObjectizer禁止以周期性消息的形式发送可变消息。
它产生了什么影响?
对于可变消息,结果出乎意料。 我们
在C ++ Russia-2017上有关SObjectizer的
报告的讨论中将其添加到SObjectizer中。 感觉到:“好吧,如果他们问的话,那么有人需要它,所以值得一试。” 嗯,他们对广泛需求的希望并不大。 尽管为此,我不得不“抽竹子”很长时间,然后才想到如何在不破坏兼容性的情况下向SO-5.5添加可变消息。
但是,当可变消息出现在SObjectizer中时,事实证明,用于它们的应用程序很少。 而且这种可变消息经常被令人惊讶地使用(可以
在关于Shrimp演示项目的故事的第二部分中找到有关此消息的信息)。 因此在实践中,此功能非常有用,因为 它使您可以解决以下问题:如果没有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 {
但是这些是简单的状态机。 各国不能相互嵌套。 不支持状态进入和退出处理程序。 在该州度过的时间没有任何限制。
即使对状态机的这种有限支持也很方便,我们使用了一年多。 但有一点,我们想要更多。
变成了什么?
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 ); } };
我们已经
在另一篇文章中描述了所有这些
内容 ;无需重复它。
当前不支持正交状态。 但是这个事实有两个解释。 首先,我们试图提供这种支持,并面临许多困难,要克服这些困难在我们看来太昂贵了。 其次,还没有人要求正交状态。 当被询问时,请回到本主题。
它产生了什么影响?
有一种感觉,它是非常严重的(尽管我们当然是主观和偏见的)。 毕竟,当您在主题领域遇到复杂的有限状态机时,您开始寻找解决方法,简化某些事情,在某些事情上花费更多的精力,这是一回事。 当您可以将对象从应用程序映射到C ++代码时,几乎是一对一的情况,这是完全不同的事情。
另外,根据所提出的问题(例如,根据状态中输入/输出处理程序的行为)判断此功能的使用。
mchain的
怎么了?
这是一个有趣的情况。 经常使用SObjectizer,以便仅使用SObjectizer编写应用程序的一部分。 通常,应用程序中的其余代码可能与参与者(尤其是SObjectizer)无关。 例如,一个GUI应用程序,其中SObjectizer用于某些后台任务,而主要工作在该应用程序的主线程上执行。
在这种情况下,事实证明,从非SObjectizer部分到SObjectizer部分,发送信息是如此简单:调用普通的发送函数就足够了。 但是,向相反方向传播信息并不是那么简单。 在我们看来,这不好,您应该在应用程序的SObjectizer部分与直接使用的非SObjectizer部分之间建立一些便利的通信渠道。
变成了什么?
因此,消息链,或更常见的表示法是,mchains出现在SObjectizer中。
Mchain是单用户邮箱的一种特定变体,其中消息是通过常规发送功能发送的。 但是,要从mchain提取消息,您无需创建代理并对其进行签名。 即使在内部代理甚至外部代理中,也可以调用两个特殊函数:receive()和select()。 第一个可以仅从一个通道读取消息,而第二个可以一次从多个通道读取消息:
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月 。 因此,特别是关于如何使用mchain的主题,我们在这里不做进一步介绍。
它产生了什么影响?
在SObjectizer-5.5中出现了mchains之后,事实证明,与以前相比,SObjectizer实际上不再是一个“角色”框架。 除了支持Actor模型和Pub / Sub之外,SObjectizer还增加了对CSP(通信顺序过程)模型的支持。 Mchains允许您在SObjectizer上开发相当复杂的多线程应用程序,而无需任何参与者。 对于某些任务,这不但方便。 我们自己不时使用的内容。
邮件限制机制
怎么了?
行为者模型最严重的缺点之一就是容易发生超载。 在发送方以比接收方处理消息更快的速度向接收方发送消息的情况下,很容易发现自己。
通常,在参与者框架中发送消息是非阻塞操作。 因此,当出现一对“灵巧的生产者和讨厌的消费者”时,接收者演员的队列将增加,同时至少存在某种类型的可用内存。
这个问题的主要困难是,对于所应用的任务和主题区域的特性,应加强防止过载的良好机制。 例如,了解可以复制哪些消息(因此能够安全地丢弃重复消息)。 了解无论如何都不能丢弃哪些消息。 谁可以被暂停以及被暂停多少,以及根本不允许谁。 等等
另一个困难是不一定总是有一个好的防御机制。 有时候,拥有一些原始但足够有效,“开箱即用”且易于使用的东西就足够了。
为了不强迫用户执行过载控制,只需简单地抛出“额外”消息或将这些消息转发给其他代理即可。变成了什么?
只是为了在简单的情况下可以使用现成的过载保护工具,即所谓的 邮件限制。这种机制使您可以丢弃不必要的消息,或将其发送给其他收件人,甚至在超出限制时甚至中断应用程序。例如:
class worker : public so_5::agent_t { public: worker(context_t ctx) : so_5::agent_t{ ctx
在另一篇文章中更详细地描述了此主题。它产生了什么影响?
这并不是说消息限制的出现已经从根本上改变了SObjectizer,其工作原理或与之合作的事物。相反,它可以与备用降落伞进行比较,后者仅用作最后的手段。但是,当您必须使用它时,您甚至可以高兴地发现它的存在。邮件传递跟踪机制
怎么了?
SObjectizer-5是开发人员的黑匣子。在其中发送消息并...消息要么到达收件人,要么不到达。如果消息未到达接收者,则用户将面临进行令人兴奋的任务以寻找原因的需求。在大多数情况下,原因很简单:将消息发送到错误的mbox或未进行订阅(例如,用户在代理的一种状态下进行了订阅,而在另一种状态下却忘记了进行订阅)。但是,可能存在更复杂的情况,例如,消息被过载保护机制拒绝。问题在于消息传递机制隐藏在SObjectizer Run-Time的内脏深处,因此,即使是SObjectizer的开发人员也很难将消息路由到收件人,更不用说用户了。尤其是对于犯了此类小错误最多的新手用户。变成了什么?
在SObjectizer-5.5中,添加了一种用于跟踪消息传递过程的特殊机制,称为消息传递跟踪(或简称为msg_tracing),然后对其进行了最终确定。在另一篇文章中更详细地描述了这种机制及其功能。因此,现在,如果邮件在传递时丢失,您只需启用msg_tracing并查看发生这种情况的原因。它产生了什么影响?
用SObjectizer编写的调试应用程序变得更加简单和有趣。即使是为了我们自己。env_infrastructure和单线程env_infrastructures的概念
怎么了?
我们一直将SObjectizer视为简化多线程代码开发的工具。因此,最初编写的SObjectizer-5版本仅在多线程环境中工作。这表示为在多线程环境中工作时,使用SObjectizer内部的同步原语来保护SObjectizer的内部。因此,它是在SObjectizer自身内部创建几个辅助工作线程(以执行诸如服务计时器和完成代理合作注销的重要操作)。即
SObjectizer是为多线程编程和在多线程环境中使用而创建的。那完全适合我们。但是,由于SObjectizer是“野外使用”的,因此发现了这样一种情况,即任务足够复杂,行动者可以在其解决方案中使用它。但是,与此同时,所有工作都可以而且必须在一个工作流程上执行。我们面临一个非常有趣的问题:是否可以教SObjectizer在单个工作线程上工作?变成了什么?
事实证明这是可能的。我们花了很多钱,花了很多时间和精力来提出解决方案。但是解决方案是发明的。引入了诸如环境基础结构之类的概念(或略微缩写的env_infrastructure)。Env_infrastructure承担了管理内部SObjectizer厨房的任务。他特别解决了诸如计时器维护,合作社注册和注销等问题。对于SObjectizer,已经做出了几个单线程env_infrastructures选项。这使我们能够在SObjectizer上开发单线程应用程序,在其中内部有正常的代理程序可以互相交换常规消息。我们在另一篇文章中更详细地讨论了此功能。 。它产生了什么影响?
实施此功能期间发生的最重要的事情也许是打破了我们自己的模板。对SObjectizer的了解永远不会相同。这么多年将SObjectizer 专门视为开发多线程代码的工具。再来一次!并且发现SObjectizer上的单线程代码也可以开发。生活充满惊喜。运行时监控工具
怎么了?
SObjectizer-5不仅在消息传递机制方面是一个黑匣子。但是也没有办法找出应用程序中当前正在运行多少个代理,创建了多少个调度程序,涉及多少个工作线程,调度程序队列中正在等待多少条消息等。所有这些信息对于监视24/7运行的应用程序非常有用。但是对于调试,我还想不时了解队列是在增长还是代理数量在增加/减少。不幸的是,目前,我们的手简直还没到为SObjectizer增加资金来收集和传播此类信息的地步。变成了什么?
在SObjectizer-5.5的某一时刻,出现了用于运行时监视SObjectizer内部的工具。默认情况下,运行时监视是禁用的,但是如果启用它,则消息将定期发送到特殊的mbox,其中将包含有关代理和合作数量,计时器数量,调度程序拥有的工作线程的信息(并且将已经有关于队列中的消息数,绑定到这些线程的代理数)。另外,随着时间的流逝,有可能额外启用有关代理在事件处理程序中花费多少时间的信息的收集。这使您可以检测某些座席太慢(或浪费时间阻止呼叫)的情况。它产生了什么影响?
在我们的实践中,不经常使用运行时监视。但是,当您需要它时,您就会意识到它的重要性。确实,如果没有这种机制,就不可能(非常困难或非常困难)弄清楚什么和如何不起作用。因此,这是“您可以做到”类别中的一项功能,但我们认为它的存在会立即将仪器转移到另一个重量类别。因为
在“膝盖上”制作一个演员框架的原型并不是那么困难。许多人已经做到了,还有更多人会做到。但是,然后为您的开发配备诸如运行时监视之类的东西……到目前为止,并非所有膝上草稿都能幸免。一行中还有一件事
四年来,SObjectizer-5.5进行了许多创新和变更,即使是摘要,对它们的描述也将占用太多空间。因此,我们用字面一行来表示它们的一部分。随机排列,没有任何优先级。SObjectizer-5.5添加了对CMake构建系统的支持。现在,可以将SObjectizer-5构建为动态库和静态库。现在已构建SObjectizer-5.5并在Android上运行(通过CrystaX NDK和全新的Android NDK)。私人调度员已经出现。现在,您可以创建和使用其他人看不到的调度程序。实现了投放过滤器机制。现在,从MPMC-mboxes订阅消息时,您可以禁止传递您不感兴趣的消息。创建和注册合作关系的工具已大大简化:方法Introduction_coop / introduction_child_coop,make_agent / make_agent_with_binder就是这样。出现了锁对象工厂的概念,现在您可以选择所需的锁对象(基于互斥锁,自旋锁,组合锁或其他)。出现了wrapped_env_t类,现在您不仅可以使用so_5 :: launch()在应用程序中运行SObjectizer。出现了stop_guards的概念,现在您可以影响SObjectizer的关闭过程。例如,您可以阻止SObjectizer在某些代理完成其应用程序工作之前停止。现在,您可以截获已传递给代理但未被代理处理的消息(所谓的dead_letter_handlers)。有机会将邮件包装在特殊的“信封”中。信封可以携带有关邮件的其他信息,并且在邮件传递给收件人时可以执行某些操作。从5.5.0到5.5.23
以代码/测试/示例的方式查看路径也很有趣。这是cloc实用程序告诉我们的有关SObjectizer-5.5.0内核代码的信息: -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C / C ++标题58 2119 5156 5762
C ++ 39 1167 779 4759
红宝石2 30 2 75
-------------------------------------------------- -----------------------------
总和:99 3316 5937 10596
-------------------------------------------------- -----------------------------
这是同一件事,但对于v.5.5.23(其中1147行是optional-lite库的代码): -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C / C ++标题133 6279 22173 21068
C ++ 53 2498 2760 10398
CMake 2 29 0 177
红宝石4 53 2 129
-------------------------------------------------- -----------------------------
和:192 8859 24935 31772
-------------------------------------------------- -----------------------------
v.5.5.0的测试量: -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C ++ 84 2510 390 11540
红宝石162496496 0 1054
C / C ++标题1 11 0 32
-------------------------------------------------- -----------------------------
总和:247 3017 390 12626
-------------------------------------------------- -----------------------------
v.5.5.23的测试: -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C ++ 324 7345 1305 35231
红宝石675 2,353 0 4,671
CMake 338 43 0 955
C / C ++标题11107 3448
-------------------------------------------------- -----------------------------
和:1348 9848 1308 41305
好吧,v.5.5.0的示例: -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C ++ 27765463 3322
红宝石28 95 0 192
-------------------------------------------------- -----------------------------
总和:5586046346314
它们是,但对于v.5.5.23已经: -------------------------------------------------- -----------------------------
语言文件空白注释代码
-------------------------------------------------- -----------------------------
C ++ 67 2141 2061 9341
红宝石133451 0868
CMake 67 93 0 595
C / C ++标题1 12 11 32
-------------------------------------------------- -----------------------------
和:268 2697 2072 10836
几乎到处都增加了近三倍。SObjectizer的文档数量可能甚至增加了更多。近期(不仅是将来)的计划
大约一个月前在这里描述了 5.5.23版本发布之后SObjectizer的初步开发计划。从根本上说,它们没有改变。但是有一种感觉是,计划将在2019年初发布的5.6.0版本将被定位为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或SObjectizer不够用,请告诉我们。我们总是听取建设性的批评。并且,如果有力所能及的话,我们就能实现用户的愿望。