Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

MotionPtr.h

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #ifndef INCLUDED_MotionPtr_h_
00003 #define INCLUDED_MotionPtr_h_
00004 
00005 #include "Shared/Resource.h"
00006 #include "Events/EventRouter.h"
00007 #include "MMAccessor.h"
00008 #include "IPC/SharedObject.h"
00009 #include "Shared/debuget.h"
00010 #include <stack>
00011 
00012 //! A shared_ptr for MotionCommands; provides allocation and access to a MotionCommand, ensuring mutually exclusive access and scope-based deallocation
00013 /*! The MotionPtr class performs several jobs:
00014  *  - Maintain a reference to the enveloping shared memory region, via SharedObject
00015  *  - Access to object within a shared memory region, enforcing mutual exclusion via MMAccessor
00016  *
00017  *  The first point means that even if the motion is removed from the motion manager or dereferenced by an
00018  *  external source, your MotionPtr can still access the motion without error.  Further, since the motion's MC_ID
00019  *  is stored in the motion, user code can test whether a motion is currently active or not.  A corollary is
00020  *  that when this class goes out of scope, the memory region will also be dereferenced, preventing leaks.
00021  *  To enforce this more rigidly, if the MotionPtr detects that the memory region it is dereferencing is
00022  *  only referenced by the MotionManager, it will remove the motion from the MotionManager as well.
00023  *
00024  *  The second job addresses the concern that the Motion process may be querying the motion
00025  *  for output values while you're in the middle of updating the motion command's parameters.
00026  *  By overloading operator-> to return an MMAccessor, each access to the motion is protected
00027  *  by a mutual exclusion lock for the scope of access.  Note that if you want to maintain a lock
00028  *  over several lines (for either performance or because the motion is left in a bad state between
00029  *  the calls), then you can use MarkScope to maintain the lock:
00030  *  @code
00031  *  MotionPtr<Foo> foo;
00032  *  foo->bar(); // a lock is obtained for the duration of this call
00033  *  foo->tweak(); // a second lock is obtained for this call
00034  *  @endcode
00035  *
00036  *  Alternatively:
00037  *  @code
00038  *  MotionPtr<Foo> foo;
00039  *  MarkScope lockfoo(foo); // a lock is obtained until 'lockfoo' goes out of scope
00040  *  foo->bar(); // the lock is maintained across these calls...
00041  *  foo->tweak();
00042  *  @endcode
00043  *
00044  *  The motion in MotionPtr is always "valid", and will be allocated using the templated motion's default
00045  *  constructor at the first access.  If you wish to call a different constructor for the motion, you can
00046  *  pass a SharedObject created with the custom constructor calls to either MotionPtr's constructor or
00047  *  using operator=:
00048  *  @code
00049  *  MotionPtr<Foo> foo; // technically, no Foo has been allocated yet, but that's transparent
00050  *  foo->bar(); // first access, now the allocation actually occurs using default constructor
00051  *  foo = SharedObject<Foo>(1,2,4); // replacing the motion, this one constructed by Foo(1,2,4)
00052  *  
00053  *  MotionPtr<Foo> foo2(SharedObject<Foo>(8,16,32)); // use motion initialized by Foo(8,16,32)
00054  *  @endcode
00055  *  
00056  *  You can share a motion between multiple MotionPtr instances, thanks to reference counting performed
00057  *  on the underlying memory region.  When a SharedObjects, or another MotionPtrs, is assigned to a MotionPtr,
00058  *  a shallow copy is performed internally, so they all reference a common motion command:
00059  *  @code
00060  *  // ...continuing previous sample
00061  *  foo = foo2; // now foo and foo2 both refer to the Foo(8,16,32) instance
00062  *  @endcode
00063  *
00064  *  One advanced feature is that motions can have their memory region reset between activations
00065  *  by calling #retain(false).  If motions are not retained, this means that if the motion
00066  *  is removed from the motion manager, the MotionPtr will release its reference to the memory region.
00067  *  If the motion is accessed again thereafter, a new one will be created.  Note that it is only a reference
00068  *  counting operation: if external references to the unretained motion remain, the original motion will
00069  *  survive, it just won't be accessible by this MotionPtr anymore unless reassigned.
00070  *
00071  *  Another advanced technique is if you @e really want to "leak" a motion, such as a self-pruning motion
00072  *  to restore some state as the behavior exits, or if another behavior holds only a MC_ID for the motion
00073  *  (thus there is no explicit reference to the underlying memory region), then you can extract the motion by:
00074  *  @code
00075  *  MotionPtr<Foo> foo; // motion you want to extract
00076  *  SharedObject<Foo> tmp = foo; // make a new reference to the region
00077  *  foo.clear() // drop foo's reference now
00078  *  // SharedObject will still dereference foo's memory region as it leaves this scope,
00079  *  // but assuming the motion is actually active, the motion manager holds a reference, so the
00080  *  // motion will survive (otherwise the memory will still be released as usual)
00081  *  @endcode
00082  *
00083  *  However, ideally MotionPtrs should be used for extended shared access to a motion (instead of sharing just the MC_ID).
00084  *  And if you are adding a "dangling" motion during exit, you should just call motman->addPrunableMotion
00085  *  directly with a SharedObject instead of using this class.
00086  */
00087 template<class T>
00088 class MotionPtr : public Resource, public EventListener {
00089 public:
00090   //! Constructor, defaults to an empty SharedObject instance and will retain the allocation once it is created
00091   explicit MotionPtr() : Resource(), EventListener(), mcObj(SharedObjectBase::NoInit()), checkouts(), retained(true) {}
00092   //! Constructor, adopts a reference to @a obj, and will retain the reference between additions to the motion manager
00093   MotionPtr(const SharedObject<T> obj) : Resource(), EventListener(), mcObj(obj), checkouts(), retained(true) {}
00094   //! Destructor, if this holds the last non-motion manager reference to the motion, removes it from the motion manager
00095   ~MotionPtr() {
00096     if(erouter!=NULL) // allows use of statics in libtekkotsu without crashing on destruction
00097       erouter->removeListener(this,EventBase::motmanEGID);
00098     ASSERTRET(ProcessID::getID()!=ProcessID::MotionProcess,"How is ~MotionPtr running in motion process?");
00099     if(active()) {
00100       MotionManager::MC_ID mcid = mcObj->getID();
00101       int rc = mcObj.getRegion()->NumberOfReference();
00102       if(motman->hasReference(ProcessID::MotionProcess,mcid))
00103         --rc;
00104       if(rc==2 && motman->hasReference(ProcessID::MainProcess,mcid)) // 2 = 1 for this + 1 for the process's MotionManager
00105         motman->removeMotion(mcObj->getID()); // last non-MotionManager reference, kill the MC
00106     }
00107   }
00108   
00109   //! Automatically exposes the underlying shared object so you can pass it to SharedObject-based functions (e.g. BehaviorBase::addMotion() or MotionManager::addPersistentMotion())
00110   operator const SharedObject<T>&() const {
00111     if(!allocated())
00112       const_cast<MotionPtr&>(*this) = SharedObject<T>();
00113     return mcObj;
00114   }
00115   //! Automatically create MMAccessor instance for mutual-exclusion access
00116   /*! This lets you assign the MotionPtr to a MMAccessor, so that the lock persists under the new scope and name.
00117    *  You could also use MarkScope with the MotionPtr. */
00118   operator MMAccessor<T>() const {
00119     if(!allocated())
00120       const_cast<MotionPtr&>(*this) = SharedObject<T>();
00121     return *mcObj;
00122   }
00123   //! Forward smart-pointer style access to MMAccessor; this is the secret to one-line locking
00124   /*! Keep in mind you can use MarkScope with the MotionPtr to retain the lock over the course of multiple accesses */
00125   MMAccessor<T> operator->() const { return MMAccessor<T>(*this); }
00126   //! Forward smart-pointer style access to MMAccessor, just for completeness vs. operator->, which is what you'll want to use
00127   /*! This is actually a little syntactically wrong, because operator*() should return a reference vs. operator->'s pointer, 
00128    *  but then we would lose the locking guarantee. */
00129   MMAccessor<T> operator*() const { return MMAccessor<T>(*this); }
00130   
00131   //! Reassigns the motion reference; henceforth shall share the region with @a obj (shallow copy)
00132   /*! Internal reference counting by the shared memory region will keep everything happy :) */
00133   MotionPtr& operator=(const SharedObject<T>& obj) {
00134     if(active()) {
00135       if(!retained)
00136         erouter->removeListener(this,EventBase::motmanEGID,mcObj->getID(),EventBase::deactivateETID);
00137       if(mcObj.getRegion()->NumberOfReference()==3) // 3 = 1 for this + 1 for Main's MotionManager + 1 for Motion's Motion Manager
00138         motman->removeMotion(mcObj->getID()); // last non-MotionManager reference, kill the MC
00139     }
00140     mcObj = obj;
00141     if(active() && !retained)
00142       erouter->addListener(const_cast<MotionPtr*>(this),EventBase::motmanEGID,mcObj->getID(),EventBase::deactivateETID);
00143     return *this;
00144   }
00145   //! Reassigns the motion reference; henceforth shall share the region with @a wr (shallow copy)
00146   /*! Internal reference counting by the shared memory region will keep everything happy :) */
00147   MotionPtr& operator=(const MotionPtr& wr) const { operator=(wr.mcObj); }
00148   
00149   //! Returns the motion command ID associated with the motion, or MotionManager::invalid_MC_ID if the motion is not allocated or otherwise not registered with the motion manager
00150   virtual MotionManager::MC_ID getID() const {
00151     if(!allocated())
00152       return MotionManager::invalid_MC_ID;
00153     return mcObj->getID();
00154   }
00155   
00156   //! Removes the reference to the current motion memory region.
00157   /*! A new one will be created with the default constructor if a future access is attempted. */
00158   void clear() { mcObj = SharedObjectBase::NoInit(); }
00159   
00160   //! Returns true if the motion is non-NULL
00161   bool allocated() const { return (mcObj.getRegion()!=NULL); }
00162   
00163   //! Returns true if the motion is registered with the motion manager (i.e. it is both allocated and has a valid MC_ID)
00164   bool active() const { return (allocated() && mcObj->getID()!=MotionManager::invalid_MC_ID); }
00165   
00166   //! Returns #retained
00167   bool retain() const { return retained; }
00168   
00169   //! Sets #retained
00170   void retain(bool r) {
00171     if(retained==r)
00172       return;
00173     retained=r;
00174     if(!retained) {
00175       if(active())
00176         erouter->addListener(this,EventBase::motmanEGID,mcObj->getID(),EventBase::deactivateETID);
00177       else
00178         clear();
00179     }
00180   }
00181   
00182 protected:
00183   virtual void useResource(Data&) {
00184     if(!allocated())
00185       *this = SharedObject<T>();
00186     if(mcObj->getID()==MotionManager::invalid_MC_ID)
00187       return;
00188     checkouts.push(mcObj->getID());
00189     motman->checkoutMotion(checkouts.top());
00190   }
00191   virtual void releaseResource(Data&) {
00192     ASSERTRET(checkouts.size()>0,"MotionPtr::releaseResource underflow");
00193     motman->checkinMotion(checkouts.top());
00194     checkouts.pop();
00195   }
00196   
00197   //! We only listen for motion deactivate events if not #retained, so if we get a call here we should clear the reference
00198   virtual void processEvent(const EventBase& event) {
00199     if(!allocated()) {
00200       std::cerr << "MotionPtr received event " << event.getDescription() << " without any motion" << std::endl;
00201       return;
00202     }
00203     if(event.getGeneratorID()!=EventBase::motmanEGID || event.getSourceID()!=mcObj->getID() || event.getTypeID()!=EventBase::deactivateETID) {
00204       std::cerr << "MotionPtr received event " << event.getDescription() << " does not correspond to deactivation of MCID " << mcObj->getID() << std::endl;
00205       return;
00206     }
00207     if(!retained) {
00208       std::cerr << "MotionPtr received motion deactivation event for " << mcObj->getID() << " even though it is retained" << std::endl;
00209       return;
00210     }
00211     clear();
00212   }
00213   
00214   //! Maintains the reference to the motion's shared memory region
00215   SharedObject<T> mcObj;
00216   
00217   //! Retains a history of useResource() calls, to be released by matching releaseResource() calls.
00218   /*! This solves the problem of removing a motion while it is locked... the removal clears the ID value in #mcObj, thus
00219    *  we need to store it here so we can checkin (unlock) the motion later.  Using a stack lets us handle recursively
00220    *  re-adding the motion and receiving a new ID while still locked under the original ID. */
00221   std::stack<MotionManager::MC_ID> checkouts;
00222   
00223   //! If false, clears the motion instance each time it is removed from the motion manager, and then recreate it if a future access occurs.
00224   /*! If true, the reference on #mcObj will be retained between additions to the motion manager, which is the default behavior. */
00225   bool retained; 
00226 };
00227 
00228 /*! @file
00229  * @brief 
00230  * @author Ethan Tira-Thompson (ejt) (Creator)
00231  */
00232 
00233 #endif

Tekkotsu v5.1CVS
Generated Mon May 9 04:58:45 2016 by Doxygen 1.6.3