Stiffstream / sobjectizer

An implementation of Actor, Publish-Subscribe, and CSP models in one rather small C++ framework. With performance, quality, and stability proved by years in the production.

Home Page:https://stiffstream.com/en/products/sobjectizer.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Classical endless loop?

ilpropheta opened this issue · comments

Hi,
this is probably a simple question but I am wondering which are your recommended answers.

I need to encapsulate a classical endless loop into a SObjectizer agent. Something like this:

class Worker
{
public:
   Worker()
   {
     m_thread = [this]{
         while (!m_stop)
         {
            // ... do something
         }
      };
   }

   void StopAndWait()
   {
      m_stop = true;
      m_thread.join();
   }

private:
   thread m_thread;
   atomic<bool> m_stop = false;
};

Also, I would like to remove "StopAndWait" and let SObjectizer handle shutdown automatically as it happens normally.

I guess this can be modeled with states. Do you recommend any common implementations?

Many thanks.

This is usually done by the following trick:

class busy_agent final : public so_5::agent_t {
  struct next_turn final : public so_5::signal_t {};

  void on_next_turn(mhood_t<next_turn>) {
    ... // The loop body is here.
    so_5::send<next_turn>(*this); // Initiate the next iteration.
  }
...
  void so_define_agent() override {
    so_subscribe_self().event(&busy_agent::on_next_turn);
  }
  void so_evt_start() override {
    so_5::send<next_turn>(*this);
  }
};

Awesome, thanks! Do you think it can make sense to provide an utility agent for this (or in so5Extra)?

I was thinking of something really simple:

class while_true_agent final : public so_5::agent_t
{
	struct next_turn final : so_5::signal_t {};

	void Step()
	{
		so_5::send<next_turn>(*this);
	}
public:
	while_true_agent(context_t c, std::function<void()> action)
		: agent_t(std::move(c)), m_action(action)
	{
	}

	void so_define_agent() override
	{
		so_subscribe_self().event([this](so_5::mhood_t<next_turn>) {
			m_action();
			Step();
		});
	}

	void so_evt_start() override
	{
		Step();
	}
private:
	std::function<void()> m_action;
};

It being understood that std::function is not a good choice, this is just to give an idea.

I never thought in that direction because such simple agents are very rare in real-life projects. More often they are used in small demos or proof-of-concept experiments where they can be written in minutes from scratch.

Imagine a plain polling like this:

while (true)
{
   // this is timed internally (e.g. we want to call "Read" as fast as possible)
   auto value = external_service.Read();
  // send value somewhere else
  //...
}

Do you think agents do not fit such scenarios?

Do you think agents do not fit such scenarios?

My personal point of view: agents work well when you have to deal with different incoming messages in a state. When you have to write something like that:

while(true) {
  check_some_condition();
  if(case_one_happened()) {...}
  check_another_condition();
  if(case_two_happened()) {...}
  check_yet_another_condition();
  if(case_three_happened()) {...}
  ...
}

then it's a clear marker that you can use some form of message-passing and agents (actors) in particular.

But in such trivial scenarios:

while (true)
{
   // this is timed internally (e.g. we want to call "Read" as fast as possible)
   auto value = external_service.Read();
  // send value somewhere else
  //...
}

agents only create additional boilerplate without any benefits.

That is my very subjective IMHO.

Maybe mchains can do what you want.

If get it wright, then you want to equip a casual thread loop with external manipulations.
Then it would look somethin like this (didn't compile or check):

 so_5::receive( so_5::from( mch ).handle_all(),
    [&](so_5::mhood_t<next_iteration>) {
          auto value = external_service.Read();
          // send value somewhere else
          //...
          so_5::send<next_iteration>( mch );
    },
    [&](so_5::mhood_t<adjust_logic >cmd ) {
        // Do something that affects main loop logic above 
    });
    
// Stop the loop somewhere on the other thread:
so_5::close_drop_content( mch );

That is my very subjective IMHO.

Many thanks for sharing your thoughts. I get your point.

I was trying to understand if SObjectizer can fit that scenario just because it's definitely a simplification to have a single way to manage concurrent objects and to encapsulate behaviors into agents. However, this comes with some trade-offs that must be taken into consideration.

Thanks @ngrodzitski for your snippet. I think @eao197 's solution is enough for my needs ;)