Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loop validated cache #79

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Loop validated cache #79

wants to merge 6 commits into from

Conversation

jdcaporale
Copy link
Contributor

@jdcaporale jdcaporale commented Mar 31, 2023

Adds a Loop-tied cache class and integrates into the control loop.

Key changes:

  • add virtual to ValidatedCache member functions for inheritance
  • LoopId class, that houses static member we use to invalidate ValidatedLoopCache class instances
    • Housed in a uint32_t, which is large enough to avoid repeats offering unique loop ids
  • ValidatedLoopCache class which auto invalidates when the control loop updates
  • MJBotsControlLoop increments LoopId before every call of Update()

Design decisions / possible issues:

  • uint32_t last_loop_id_ and LoopId loop_id_ members in the cache increases the memory overhead, especially for caching small data types, could be smaller if we use uint8_t but without the same uniqueness properties (at 1kHz, uint8_t would repeat 4 times a second)
  • Implemented operator= overload to make using caches easier, but it does hide the validation from the user and could lead to mistakes

This was tested locally on hardware using print statements in a simple control loop.

class Test_Validated_Loop_Cache : public kodlab::mjbots::MjbotsControlLoop<ManyMotorLog> {
  using MjbotsControlLoop::MjbotsControlLoop;
  kodlab::ValidatedLoopCache<float> myValue;
  void Init() override {
    myValue = 0.5;
  }
  void Update() override {
    std::vector<float> torques(num_joints_, 0);
    robot_->SetTorques(torques);
    std::cout<<kodlab::LoopId::get()<<std::endl;
    if(!myValue.valid()){
        myValue = myValue.get() + 1;
    }
    // if(myValue.valid()){
    //     myValue = myValue.get() - 1;
    // }
    std::cout<<myValue<<std::endl;
  }
  void PrepareLog() override {
    // Populate log message with data from current control loop cycle
    for (int servo = 0; servo < num_joints_; servo++) {
      log_data_->positions[servo] = robot_->GetJointPositions()[servo];
      log_data_->velocities[servo] = robot_->GetJointVelocities()[servo];
      log_data_->modes[servo] = static_cast<int>(mjbots_interface_->GetJointModes()[servo]);
      log_data_->torques[servo] = robot_->GetJointTorqueCmd()[servo];
    }

    // Fill remaining log message fields with zeros
    for (int servo = num_joints_; servo < 13; servo++) {
      log_data_->positions[servo] = 0;
      log_data_->velocities[servo] = 0;
      log_data_->modes[servo] = 0;
      log_data_->torques[servo] = 0;
    }
  }
};

Updated integer types to uint32, reordered the loop increment to
better handle the first loop (includes setting id to uint32(-1) )
Tested on hardware locally.
Copy link
Contributor

@ShaneRozenLevy ShaneRozenLevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. A few small things to discuss/document.

include/kodlab_mjbots_sdk/loop_cache.h Show resolved Hide resolved
include/kodlab_mjbots_sdk/common_header.h Outdated Show resolved Hide resolved
Copy link
Collaborator

@ethanmusser ethanmusser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Objections to LoopId, but looking good otherwise.

Comment on lines 26 to 64
class LoopId
{
public:
/**
* @brief Returns the current loop id
*
* @return uint32_t
*/
static uint32_t get(){
return loop_id_;
}

/**
* @brief Override the current loop id
*
* @param id_des
*/
static void set(uint32_t id_des){
loop_id_ = id_des;
}

/**
* @brief increment and return the loop id, note
* @note there can only be ONE loop using this type of cache at once to avoid
* prematurely invalidating caches, MAKE SURE THIS IS WHAT YOU WANT
*
* @return uint32_t
*/
static uint32_t increment(){
return (++loop_id_);
}

protected:
/**
* @brief Loop id shared between all instances acts a "global" loop id
*
*/
static uint32_t loop_id_;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a mutable singleton with some possible issues.

First, philosophically, why use a singleton here? It goes against the ISO guidelines, and the resulting behavior could instead be maintained in a local scope. E.g., a loop ID is maintained in the control loop and all ValdatedLoopCaches are constructed with a reference/pointer to that ID (and can use the same invalidation logic).

That said, since this is a pretty high-level, integral thing, then a singleton could be fine. But:

  • this singleton could suffer from the static initialization order fiasco if LoopId were to be referenced in another static variable initialization;
  • loop_id_ can be arbitrarily modified by children of LoopId;
  • LoopId can be multiply constructed (which could be confusing).

You could alleviate some of these issues by making LoopId a Meyer's Singleton. I think something like this would work.

class LoopId {
 public:
  static LoopId& get() {
    static LoopId loop_id;
    return loop_id;
  }

  static void set(uint32_t id) {
    auto loop_id = LoopId::get();
    loop_id = id;
  }

  static void increment() {
    auto loop_id = LoopId::get();
    loop_id++;
  }

  LoopId(const LoopId&) = delete;
  LoopId& operator=(const LoopId&) = delete;

private:
  LoopId() {}
  ~LoopId() {}

};

include/kodlab_mjbots_sdk/common_header.h Show resolved Hide resolved
Copy link
Contributor

@ShaneRozenLevy ShaneRozenLevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, besides why loop_cache.cpp exists.

@@ -0,0 +1,13 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just placeholder making sure the new version is good. Could move implementation into it if that is something we want, but otherwise I will delete.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm always of the opinion that the implementation should live in the source file when possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not templated, it's an easy move, so I will.

@jdcaporale
Copy link
Contributor Author

@ethanmusser Thoughts about this implementation? Took some of your advice, I think it should be ok now, and maybe even threadsafe with the addition of atomic.

{
data_ = data;
valid_ = true;
last_loop_id_ = loop_id_.get();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
last_loop_id_ = loop_id_.get();
last_loop_id_ = LoopId::get();


protected:
uint32_t last_loop_id_ = -1; // loop id from the last set event, used to invalidate
LoopId loop_id_; // Loop id, tied to control loop
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LoopId loop_id_; // Loop id, tied to control loop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants