handling user supplied functions in libraries...
I'm looking for advice on the best way to handle the current situation...
I'm writing a library (in this case a pathfinding library) for which there will be a need to execute a function external to the library and return structured results from it. I want to supply a class in the library that can be used for passing back the correct info to the library for use in its search. In my present application, I'm also writing the other set of functions (domain model evaluation functions) but I want this interface to be general so I can use it in other pathfinding problems in other domains where the evaluation functions will be different (but the search algorithms involved dictate the sort of information required from an evaluation, hence the structured communication). Does this make sense?
What I want is advice on how best to handle plugging in external functions to a library. I've been playing around with passing functors and have a working implementation along those lines, but I wanted to get more qualified advice from those experienced with this problem as to the best way to handle it so as to avoid reconfiguration (or redesign) of the library in the future.
Thanks for any and all help,
Timkin
Generally, I'd do this by providing a pure virtual function in a base class and implement it with different subclasses of all the base types:
You get the idea. You may want some runtime type checking to ensure things are valid in the findPath function for robustness.
Hope this helps
Edit: Of course you may want to specify a tolerance or maximum value for the route the pathfinding function actually comes up with - again, implement the interface in the base class.
Also because this only requires dynamic linking, it means it's possible to produce a closed-source project off this if you LGPL the code.
class BasePathPosition{ // An abstract spatial location}class BasePathNode : public BasePathPosition{ // A linked position std::vector<BasePathNode*> m_vpLinkedNodes; std::vector<float> m_pfLinkValue; // Used for range/terrain difficulty void linkTo(BasePathNode* pNode,float value);}class PathRoute{ // A collection of nodes in order of traversal std::vector<BasePathNode*> m_vpRoute; // In order start to finish float m_fTotalValue; // Total value of the route std::vector<BasePathNode*>::iterator m_itCurrentNode; // Update as entity moves}class BaseNodeMap{ BasePathNode* m_pRootNode; // Typical hierarchical node map virtual BasePathNode* findClosestNode(BasePathPosition* pPosition)=0;}class BasePathfinder{ virtual PathRoute* findPath(BaseNodeMap* pMap,PathPosition* pStart,PathPosition* pEnd)=0;}class ConcretePosition : public BasePathPosition{ float x,y,z;};class ConcreteNodeMap : public BaseNodeMap{ // This takes a ConcretePathPosition and returns a ConcretePathNode! BasePathNode* findClosestNode(BasePathPosition* pPosition);}class ConcretePathFinder{ // Call this with Concrete Positions and NodeMap! PathRoute* findPath(BaseNodeMap* pMap,PathPosition* pStart,PathPosition pEnd*);}
You get the idea. You may want some runtime type checking to ensure things are valid in the findPath function for robustness.
Hope this helps
Edit: Of course you may want to specify a tolerance or maximum value for the route the pathfinding function actually comes up with - again, implement the interface in the base class.
Also because this only requires dynamic linking, it means it's possible to produce a closed-source project off this if you LGPL the code.
Quote:Original post by _winterdyne_
Generally, I'd do this by providing a pure virtual function in a base class and implement it with different subclasses of all the base types:
So your library consists of an exported (pure virtual) base class which you require the user to derive from (and use the derived classes) in order to perform the actual search?
I'll have to give this some thought as to how it would work with my current search classes... sounds like a reasonable idea though... will post something back shortly.
Thanks,
Timkin
No - I also provide concrete implementations for common use cases (3d spatial nodes, 2d nodes). But if the need arises, the base class can be derived from to use the same interface.
You only really need to keep a pure virtual if you absolutely, definitely need to force the user of the class to do something specific - for most libraries, once you've sufficiently defined the use case surrounding your task you should just be able to provide the function as is (no virtuals in the top-level class).
You only really need to keep a pure virtual if you absolutely, definitely need to force the user of the class to do something specific - for most libraries, once you've sufficiently defined the use case surrounding your task you should just be able to provide the function as is (no virtuals in the top-level class).
Upon consideration I don't think your suggestion fits with what I'm trying to achieve (although I could be wrong). The impression I got from your example was that the user is writing the member functions for ConcretePathFinder, which is the implementation of the search.
I already have a search algorithm implemented within a library. I wont to separate the search implementation from its application. The user should be able to instantiate a search object to perform a search in a domain the user specifies through definitions of state type, action type, cost type and an action evaluation function. Then, any given search is evaluated given a starting state, goal state and initial resources... but the user doesn't have to write any of the search implementation... just choose the type of search by the search class they instantiate.
This doesn't seem to fit with what your suggesting. If my interpretation of your example is in error, please correct me. Otherwise, can you see what I'm suggesting and propose a paradigm?
Thanks,
Timkin
I already have a search algorithm implemented within a library. I wont to separate the search implementation from its application. The user should be able to instantiate a search object to perform a search in a domain the user specifies through definitions of state type, action type, cost type and an action evaluation function. Then, any given search is evaluated given a starting state, goal state and initial resources... but the user doesn't have to write any of the search implementation... just choose the type of search by the search class they instantiate.
This doesn't seem to fit with what your suggesting. If my interpretation of your example is in error, please correct me. Otherwise, can you see what I'm suggesting and propose a paradigm?
Thanks,
Timkin
I think I see what you're saying. You're after RTTI then.
Use the base class to provide the common interface for all your concrete pathfinding implementations, which you bury in the library (as per my example). I was actually suggesting that you produce at least *some* concrete implementations (just the abstract base class isn't a very functional library!).
You can rely on your user to create the appropriate type of object for their requirements:
Or if you expect your user to use different types of path in the same application, you could provide a single aggregated interface:
If you're after a user being able to insert their own types into the aggregate, this is best handled with a means of RTTI, and have the aggregate check the incoming types to ensure they match, and then call an appropriate pathfinder held in a vector - this means you need to implement RTTI at the abstract level, as pure virtual 'int getType()=0' functions, although I tend to use strings for RTTI as it's much easier to see whats going on when debugging.
Use the base class to provide the common interface for all your concrete pathfinding implementations, which you bury in the library (as per my example). I was actually suggesting that you produce at least *some* concrete implementations (just the abstract base class isn't a very functional library!).
You can rely on your user to create the appropriate type of object for their requirements:
// User code:ConcretePathFinder UserPathFinder;ConcretePosition MyPosition;ConcretePosition DesiredPosition;PathRoute* pRoute;pRoute = ConcretePathFinder.findPath(&MyPosition,&DesiredPosition)
Or if you expect your user to use different types of path in the same application, you could provide a single aggregated interface:
// Library code:class AggregatePathFinder{ ConcretePathFinderA PathFinderA; ConcretePathFinderB PathFinderB; PathRoute* findPath(ConcretePositionA* pStart,ConcretePositionA* pEnd) { return PathFinderA.getPath(pStart,pEnd); } PathRoute* findPath(ConcretePositionB* pStart,ConcretePositionB* pEnd) { return PathFinderB.getPath(pStart,pEnd); }}// User code:AggregatePathFinder PathFinder;ConcretePositionA AStart,AEnd;ConcretePositionB BStart,BEnd;PathRoute* ARoute, BRoute;// The aggregate uses the same semantics regardless of type, but types must match:// The 'A' Type pathfinder returns a PathRoute populated with ptrs to A Type nodes.ARoute = AggregatePathFinder.findPath(AStart,AEnd);// The 'B' Type pathfinder returns a PathRoute populated with ptrs to B Type nodes.BRoute = AggregatePathFinder.findPath(BStart,BEnd);
If you're after a user being able to insert their own types into the aggregate, this is best handled with a means of RTTI, and have the aggregate check the incoming types to ensure they match, and then call an appropriate pathfinder held in a vector - this means you need to implement RTTI at the abstract level, as pure virtual 'int getType()=0' functions, although I tend to use strings for RTTI as it's much easier to see whats going on when debugging.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement