mercredi 1 juin 2022

Command pattern in c++ with time constraints

I'm designing a command pattern in a c++ library. Usually the command executor has a queue of command objects, it loops over the command and it waits on a condition variable if the queue is empty. Quite classic. Here a pseudo-code:

void Executor::run() {
 //check if queue is empty
 if (queue.isEmpty()) {
     condVar.wait(...);
 }
 while(!queue.isEmpty()) {
      //execute the command 
      auto cmd = queue.take();
      cmd.run();
 }
}

This is a basic approach but it doesn't work really well with timed command where you want to execute something with time constraints. I have two different solutions.

Option 1 Use the same approach used in the android MessageQueue, so the executor has a set of commands ordered by time. Each time the executor check if command time is in the past or in the future, then it goes to sleep for difference between current time and command time.

void Executor::run() {
 //check if queue is empty
 if (queue.isEmpty()) {
     condVar.wait(...);
 }
 while(!queue.isEmpty()) {
     auto cmd = queue.take();
     if (now() < cmd.when) {
         sleepFor(now() - cmd.when); //sleep
     }
     cmd.run(); //execute
 }
}

Option 2 Instead of using absolute time I create a command scheduler with a tick and each time decrements a counter for each command, when it's zero, then execute the command.

void Executor::run() {
 //check if queue is empty
 if (queue.isEmpty()) {
     condVar.wait(...);
 }
 //for each tick
 for (auto e : queue) {
    e.dec();
    if (e.expired()) {
         e.run();
    }
 }
}

When inserted in the queue, each command calculates the number of ticks needed in relation with the requested time.

Summary

With both options the interface of the command executor can have something like pushAt or pushDelayed or push, i.e. interfaces to add commands to the queue specifying a duration or an absolute time.

Option 1: O(1) execution if we exclude the time to execute the command's run method. It requires a data structure ordered, like multiset or set, so insertion can be a demanding operation. It works with absolute time, i.e. time points.

Options 2: O(n) execution, for each tick it must loops over all commands to decrement the counter. It uses just a counter, so the execution is not affected by absolute time change. Tick affects precision of commands, granularity is tick-based. It doesn't use a strict order so insertion are not so expensive.

What's the best?

Aucun commentaire:

Enregistrer un commentaire