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
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions include/kodlab_mjbots_sdk/common_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ template<typename T>
class ValidatedCache
{

private:
protected:
/**
* @brief Validation status of \c data_
*/
Expand All @@ -71,11 +71,17 @@ class ValidatedCache
*/
ValidatedCache(const T &data, const bool &valid) : data_(data), valid_(valid) {}

/**
* @brief Destroy the ValidatedCache object. Virtual destructor for proper
* derived pointer destruction.
*/
virtual ~ValidatedCache(){};

/**
* @brief Returns the data status
jdcaporale marked this conversation as resolved.
Show resolved Hide resolved
* @return \c true if data is valid, \c false otherwise
*/
bool valid() const { return valid_; }
virtual bool valid() const { return valid_; }

/**
* @brief Marks the class invalid so that future calls will change the cached
Expand All @@ -87,19 +93,28 @@ class ValidatedCache
* @brief Sets the class data and marks it valid
* @param data valid data
*/
void set(const T &data)
virtual void set(const T &data)
{
data_ = data;
valid_ = true;
}

/**
* @brief Uses operator= to set the class data and marks it valid
* @param data valid data
*/
void operator=(const T &data)
{
set(data);
}

jdcaporale marked this conversation as resolved.
Show resolved Hide resolved
/**
* @brief Accessor for data (<b>does not check validity</b>)
* @return data
*/
T get()
{
if (!valid_)
if (!valid())
{
std::cerr << "[WARN] Returning invalid data." << std::endl;
jdcaporale marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
103 changes: 103 additions & 0 deletions include/kodlab_mjbots_sdk/loop_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @file loop_cache.h
* @author J. Diego Caporale (jdcap@seas.upenn.edu)
* @brief Control loop validated cache and id for automatic invalidation synced
* with loop
* @date 2023-03-30
*
* @copyright Copyright (c) 2023 The Trustees of the University of Pennsylvania.
* All Rights Reserved
* BSD 3-Clause License
*/

#pragma once
#include <type_traits>
#include <memory>

#include "kodlab_mjbots_sdk/common_header.h"

namespace kodlab{

/**
* @brief Simple loop id counter class that allows the user to tell when the
* control loop has looped. Useful for forcing recalculations and/or timing.
jdcaporale marked this conversation as resolved.
Show resolved Hide resolved
*
*/
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() {}

};


/**
* @brief Subclass of the ValidatedCache which uses the loop id to invalidate
* data, while still allowing the user to invalidate manually if so desired.
*
* @tparam T, data type
*/
template<typename T>
class ValidatedLoopCache : public ValidatedCache<T>
{
public:
using ValidatedCache<T>::ValidatedCache;
using ValidatedCache<T>::valid_;
using ValidatedCache<T>::data_;
using ValidatedCache<T>::operator=;

/**
* @brief Returns the data status
* @return \c true if data is valid, \c false otherwise
*/
bool valid() const override{ return (valid_ && loop_id_.get() == last_loop_id_); }

/**
* @brief Sets the class data and marks it valid and saves loop id
* @param data valid data
*/
void set(const T &data) override
{
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

};

}// namespace kodlab
4 changes: 4 additions & 0 deletions include/kodlab_mjbots_sdk/mjbots_control_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "real_time_tools/hard_spinner.hpp"
#include "VoidLcm.hpp"
#include "common_header.h"
#include "kodlab_mjbots_sdk/loop_cache.h" // Set up loop id for loop-tied caches

namespace kodlab::mjbots {
/*!
Expand Down Expand Up @@ -250,6 +251,8 @@ void MjbotsControlLoop<log_type, input_type, robot_type>::Run() {
lcm_sub_->Init();
Init();
SoftStart::InitializeTimer();
kodlab::LoopId::set(-1); // Set loop id/count to uint(-1), first update
// will be in loop 0

float prev_msg_duration = 0;

Expand Down Expand Up @@ -284,6 +287,7 @@ void MjbotsControlLoop<log_type, input_type, robot_type>::Run() {
}

// Calculate torques and log
kodlab::LoopId::increment(); // invalidate any caches that are tied to the loop by incrementing the id
jdcaporale marked this conversation as resolved.
Show resolved Hide resolved
Update(); //TODO should we give full control to the robot_ instead of feeding update thorugh?
// robot_->Update();
PrepareLog();
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(HEADER_LIST "${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/pi3ha
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/lcm_publisher.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/abstract_realtime_object.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/mjbots_control_loop.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/loop_cache.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/joint_base.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/joint_moteus.h"
"${kodlab_mjbots_sdk_SOURCE_DIR}/include/kodlab_mjbots_sdk/math.h"
Expand All @@ -41,6 +42,7 @@ set(SOURCE_LIST "pi3hat.cpp"
"robot_base.cpp"
"joint_base.cpp"
"cartesian_leg.cpp"
"loop_cache.cpp"
)

# Make an automatic library - will be static or dynamic based on user setting
Expand Down
14 changes: 14 additions & 0 deletions src/loop_cache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
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.

* @file loop_cache.cpp
* @author J. Diego Caporale (jdcap@seas.upenn.edu)
* @brief Control loop validated cache and id for automatic invalidation synced
* with loop
* @date 2023-03-30
*
* @copyright Copyright (c) 2023 The Trustees of the University of Pennsylvania.
* All Rights Reserved
* BSD 3-Clause License
*/
#include "kodlab_mjbots_sdk/loop_cache.h"

uint32_t kodlab::LoopId::loop_id_ = 0; //Initialize the static variable