John Lokanis Posted March 30, 2011 Report Share Posted March 30, 2011 How do I create a generic message passing class that allows me to map specific messages to specific receivers? Basically, think of one of those pneumatic message tubes that used to be used to pass paperwork around a large building. The tube is generic, but the message is specific and has a specific destination. The Problem: I have an application with 7 parallel loops. Each of these loops monitors a queue and when it gets a message it de-queues it and takes the appropriate action (executes the proper case). Each loop can send a message to the other 6 loops. To make this work, I create 7 queues in the top level application and then pass all 7 queue references to each loop (they are each in their own sub-vi). Also, each case in each loops uses unique data. So, the data type of the queues is a cluster of an enum (the message type) and variant (the data). In each case I have to cast the variant to the proper data type before I use it. The Solution: I want to have a single class hierarchy that can handle all messages. I want each loop to have its own child class. I want each message in each loop to be a child of the loop's child class so I can use dynamic dispatch to choose the action taken. I want a single class wire to be sent to each loop. I will initialize the parent object to create a ‘channel’ for each loop. This must be done by passing an object of the loop's child type to a create method. Inside each loop sub-vi, it will call a method to get the queue reference for the ‘channel’ it is supposed to respond to. It will do this by passing an object of the loops’ child type to the get method. When a loop receives a message, it will de-queue it and pass it to an execute method that will dispatch to the correct action. To send a message, a send method will be called and the ‘action’ object (initialized with any specific data) will be passed in. This will cause it to be placed in the correct queue for the loop that responds to that type of message. (The action object is an object of one of the loop's children) The result will be a clean way to pass messages around the application without the need to pass large sets of references to each sub-vi. Also, there will be no need to type cast the data from a variant or use enums to set the message type. To add new messages, I will just need to create a new child class of the loop’s class. No need to modify any of the rest of the class hierarchy or change any typedefs. The Question: Is this even possible? If so, how do I do it? I have been trying to figure it out for a while with little progress. It seems feasible but I am just learning LVOOP and am not sure if there is an existing design pattern for this or a way to combine existing patterns. Has anyone done something similar? Bonus feature: It would be great if this would work in parallel with multiple copies of the top level VI. In other words, all the communication channels would be unique to the top level caller and not store any data in globals of any type. Thanks for any ideas or help with this… -John Quote Link to comment
Michael Aivaliotis Posted March 31, 2011 Report Share Posted March 31, 2011 See here: http://decibel.ni.co.../docs/DOC-15014 Specifically look at the UI Framework code. I think it kinda does what you are asking. Each message is a child class. This presentation was given at the CLA summit this year. I am very interested in this design pattern as well. Quote Link to comment
John Lokanis Posted March 31, 2011 Author Report Share Posted March 31, 2011 I might be missing something, but those examples seem to demonstrate the Factory Pattern but do not address the messaging problem I am trying to solve. Quote Link to comment
Michael Aivaliotis Posted March 31, 2011 Report Share Posted March 31, 2011 I might be missing something, but those examples seem to demonstrate the Factory Pattern but do not address the messaging problem I am trying to solve. Really? Did you open up the code? UI Plugin Framework Solution.zip UI Framework (PC-QSM).zip They are also referenced here: http://decibel.ni.co.../docs/DOC-12645 I don't think it's an exact match to your proposed solution but it has the right elements. Quote Link to comment
Daklu Posted March 31, 2011 Report Share Posted March 31, 2011 I don't think it's an exact match to your proposed solution but it has the right elements. It does have the command pattern part of the solution, but it's still only a point-to-point solution. John (if I'm understanding correctly) is asking about a single wire message bus supporting multiple queues with automatic routing based on message subclass type... and the command pattern. How do I create a generic message passing class that allows me to map specific messages to specific receivers? Basically, think of one of those pneumatic message tubes that used to be used to pass paperwork around a large building. The tube is generic, but the message is specific and has a specific destination. I've attached an example that shows how to implement the general behavior you're asking about. (It does depend on the LapDog Message Library.) Some of the implementation is different. Specifically, you mentioned dynamic dispatching on the message type to choose which queue to enqueue or dequeue the message to. I can't think of a clean way to do that. This implementation uses named queues to associate a message type with a queue. Each loop's message class overrides Get DestinationQueue and returns the name of the queue assigned to that loop. This is the main vi, and it illustrates what I don't like about this particular pattern. If you imagine each of the four loops as separate components, you'll notice they each contain class cubes or vis "owned" by other loops. This creates source code dependencies between the components... tightening the coupling and making the app harder to maintain later. The messaging system (IMO) should help keep components independent from each other, not tie them together more. The QueueCollection.AddQueue method queries the input message for it's queue name and adds that queue to the collection. Obviously this should check to make sure the queue doesn't already exist. EnqueueMessage (and DequeueMessage) iterate through the array of queues and return the one with the name that matches the message's queue name. AutoMessageRouting.zip Quote Link to comment
Tim_S Posted March 31, 2011 Report Share Posted March 31, 2011 Thanks for any ideas or help with this… I'll have to look at the code sugested when I get a moment, however it seems like this is a network in the software similar to UDP, RS-422 or RS-485 in that everyone has to receive the message, determine if it's addressed to them and act accordingly. Alternatively there could be code acting similar to an Ethernet switch in the middle, which would look at the destination of the address and determine a direction to send it. Tim Quote Link to comment
Jon Kokott Posted March 31, 2011 Report Share Posted March 31, 2011 (edited) Here is my suggestion: 2 Class hierarchies Hierarchy 1. The first hierarchy is a class containing the 7 queues you are interested in using (This class does not need to be a class, its only reason is extensibility, generally if you don't use the dynamic dispatching feature, use a lvlib) Hierarchy 2 class hierarchy with a placeholding parent (must override) children are the datatypes you want to move to dequeue destinations.) dyanmic dispatch VI for enqueue (one input for the class in hierarchy one) once you are in the dynamic dispatch VI, select the Queue from the first class containing all the references (property node read, read member VI, unbundle, whatever) Why this is better: Its alot faster than testing for named queues (even if there are only 7, especially if there are 1000s.) Naming queues is really really bad practice if you ask me. ~Jon Edited March 31, 2011 by Jon Kokott Quote Link to comment
PaulL Posted March 31, 2011 Report Share Posted March 31, 2011 John, The way I would approach your task is to use the Command Pattern, including an object representing the Target as an attribute in the Command object I send as a message. Hence you could have a top-level Command class, and its private data could include a Context object. Subclass Command into the particular commands, and Context into the particular contexts. Then each loop gets all the messages (not quite what you asked) but only acts on those that apply. (An Invoker class can handle this.) Further, if your loops are really clones of one another (not sure if this is the case), you could actually use a single controller, and just have instance Model objects that contain the state of the particular instance. Then you have something really quite elegant. (All of this is very straightforward if you've done it once. It can be challenging otherwise. I can explain further if you're interested.) If each loop really must get a specific set of messages you can use point-to-point communications or, what I suggest, a publish-subscribe paradigm (e.g., with Networked Shared Variables, with one variable for each loop [Context]). My biggest concern (and it's not a big one) would be having multiple writers (6, in fact) on each channel. That's actually OK as long as the application won't get confused. Paul Quote Link to comment
John Lokanis Posted March 31, 2011 Author Report Share Posted March 31, 2011 Where can I find an example of the Command Pattern? It does have the command pattern part of the solution, but it's still only a point-to-point solution. John (if I'm understanding correctly) is asking about a single wire message bus supporting multiple queues with automatic routing based on message subclass type... and the command pattern. I will take a look at the code to see if this works. Definitely do not want to use named queues, however. Is there some way to have an array of queues of child objects and then create a method that I can pass in a child object and compare it to all the queues in the array to find the one that uses the same data type? I think this is the key and might not be possible. Here is why: 1. the array of queues will need to be stored in the parent as an array of variant since they types are incompatible. 2. The input to the 'find' method will need to be of type parent class so any child can be passed in, but then I need to cast it to the child type, create a queue of that type and then try to cast each variant in the parent's array to the new type of 'queue of child' until I find one that works. The problem is, I don't think there is any way to cast the object passed in to its child type without knowing what it is first! Some ideas are: try casting it to each child type, but how do you get a list of all child types and an example of each? Or, do you make the find method dispatch to the child, passing it the array of variant and have it return the correct item in the array? And then, how do you convert that to the proper child typed queue ref? Can a child access the array in the parent's datatype, use it in the dynamic dispatched code and then put it back in the parent array? this is getting quite convoluted... Quote Link to comment
Daklu Posted March 31, 2011 Report Share Posted March 31, 2011 Naming queues is really really bad practice if you ask me. I agree and don't use them in my code. It was just the easiest way to illustrate the key-value concept of identifying the correct queue in the array. once you are in the dynamic dispatch VI, select the Queue from the first class containing all the references (property node read, read member VI, unbundle, whatever) Your solution might work, but I'm missing a few things. Couple questions: 1. How are you storing the queues in the queue collection class (the first class?) Storing each one as a separate control isn't very scalable, especially if you have 1000's of queues. The best solution will allow an arbitrary number of queues. 2. How does the message class (the dynamic dispatch vi) know which queue to get from the queue collection class (the first class) at runtime? It needs to have some way to tell the queue collection which queue to put the message on. 3. How does the loop dequeue the message? The problem is, I don't think there is any way to cast the object passed in to its child type without knowing what it is first! There is, but it might not help you in this case. The 'Preserve Run-Time Class' prim downcasts based on object type, whereas the 'To More Specific Class' prim downcasts based on wire type. I don't know what will happen at run time if you feed the PRT output into the Obtain Queue prim. I suspect you'll get a run time queue of the child type. In any event, you're resorting to some (as you suggested) obscure code for (imo) very little gain. Can a child access the array in the parent's datatype, use it in the dynamic dispatched code and then put it back in the parent array? Yes, if the parent has accessor methods. I often create protected static accessor methods specifically for allowing children access to parent data. Quote Link to comment
John Lokanis Posted March 31, 2011 Author Report Share Posted March 31, 2011 Ok, here is what I am thinking: Create A Queue: Child (channel type) overrides parent dynamic dispatch method. In the child method, it gets the array of variant from the parent (must block updates). It then creates a queue of type 'child', converts this to a variant and then adds it to the parent's array, writing the result back and unlocking the parent's data. Get the Queue for a specific channel: Child overrides parent dynamic dispatch method. In the child method, it gets a copy of the array of variant from the parent (no blocking). It then creates a queue of type child and uses that to cast each variant in the array until it succeeds. It returns the found queue as a variant (since it cannot return it as the specific queue ref or the datatypes would not be consistent). Convert Queue Ref Variant to Actual Queue Ref: Each child needs to have a method that converts the variant of the queue ref to an actual working queue ref. This can be done similar to how we tested each queue ref variant in the Get method. This cannot be a dynamic dispatch method since it must have an output unique to the child's type. But each child class must include one of these. This will only be used inside the loop that responds that child's channel and since we know we are building this loop for this purpose, that is ok. Send Message: This will be similar to the Get Queue method. In this case, we are sending an object of type 'child of channel' or child of the child (grandkid). This needs to somehow dispatch to a method of the child (channel) that does something similar to the Get but instead of returning the variant, it enqueues the grandkid object in the proper queue for the channel and returns nothing. Obviously the sender needs to know the type of message and needs to call any init methods to setup the data in the message before passing it to the send. Execute Message: This will be a dynamic dispatch method that dispatches from the parent down to the grandkid, where the specific implementation for this message is contained. I realize this all sounds complex, but once it is constructed, I think it will scale well. You can add as many channels as you want. You can add messages (behaviors) by adding grandkid classes to any channel, without touching the code above it. The main top level VI will simply initialize a channel for each loop (could be done inside the sub-vi that holds the loop). The queue itself will be hidden except in the loop subvi where it needs to wait for a message. I suppose this could be put inside a method as well. so, where are the holes in my plan? Quote Link to comment
ShaunR Posted March 31, 2011 Report Share Posted March 31, 2011 Or you could just use a string (SCPI format works well) and save yourself 3 weeks work. Quote Link to comment
Jon Kokott Posted March 31, 2011 Report Share Posted March 31, 2011 I agree and don't use them in my code. It was just the easiest way to illustrate the key-value concept of identifying the correct queue in the array. Your solution might work, but I'm missing a few things. Couple questions: 1. How are you storing the queues in the queue collection class (the first class?) Storing each one as a separate control isn't very scalable, especially if you have 1000's of queues. The best solution will allow an arbitrary number of queues. 2. How does the message class (the dynamic dispatch vi) know which queue to get from the queue collection class (the first class) at runtime? It needs to have some way to tell the queue collection which queue to put the message on. 3. How does the loop dequeue the message? There is, but it might not help you in this case. The 'Preserve Run-Time Class' prim downcasts based on object type, whereas the 'To More Specific Class' prim downcasts based on wire type. I don't know what will happen at run time if you feed the PRT output into the Obtain Queue prim. I suspect you'll get a run time queue of the child type. In any event, you're resorting to some (as you suggested) obscure code for (imo) very little gain. Yes, if the parent has accessor methods. I often create protected static accessor methods specifically for allowing children access to parent data. 1. The confines of your example as well as mine are both constructed such that one queue applies per class. There are much more elegant solutions available but given the starting point I kept the construction equally simple to your presented solution. 2. Again they are explicit located in the dynamic dispatch. The collection class and the data classes are NOT decoupled, they don't have to be. (hes not writing the end all be all messaging protocol.) 3. With a dequeue? I don't understand the question. Quote Link to comment
Francois Normandin Posted March 31, 2011 Report Share Posted March 31, 2011 Hi John, The way I've gone about this with OOP in the past is to use Events. Now, I don't know how well this solution would work for you, but basically, it's a framework by which each loop would subscribe. The framework is based on very simple code I put in the CR a while back: LVOOP Event Handler. If you're not talking about lots of data (high streaming), it could be worth exploring. I've done an example (attached code) for which you'll have to install the LVOOP Event Handler package for it to work. Messager.zip In essence, it takes a message hierarchy with a single dynamically dispatched VI to handle each cases, plus let's call it a "Loop" hierarchy to run each loop asynchronously. The parent Loop.lvclass will take care of registering each loops in the Event Handler. Then each loop needs to be a child class of this common ancestor. The only code that needs to be duplicated in the child is the way to talk with the Event Handler (3 event cases). It's all controlled with a "Global Stop" that is part of the Event Handler framework. Each loop calls the parent "Run.vi" that will initialize the messaging events for each loops. It does it by scanning all available loops classes on disk. Plus, it creates an event to dispatch a message to all active loops. This should create n*(n+1) events in the handler. Each loops overrides Run.vi, in which you have the event management plus you put your normal code. The events cases are "Global Stop" which gets the signal that the main VI has called for a shutdown, "Handler Activity" which gets called whenever a new loop is created and has added new registrable events and finally, "Subscribable Events" by which you will get the message when generated by another loop. As you can see from the project layout, the hierarchy is very simple. Copy one of the loop child classes and modify the Run vi. That's it. <object id="scPlayer" width="1376" type="application/x-shockwave-flash" data="http://content.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/jingh264player.swf" height="818"> <param name="movie" value="http://content.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/jingh264player.swf"> <param name="quality" value="high"> <param name="bgcolor" value="#FFFFFF"> <param name="flashVars" value="thumb=http://content.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/FirstFrame.jpg&containerwidth=1376&containerheight=818&content=http://content.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/quick_demo.mp4&blurover=false"> <param name="allowFullScreen" value="true"> <param name="scale" value="showall"> <param name="allowScriptAccess" value="always"> <param name="base" value="http://content.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/"> <iframe type="text/html" style="overflow: hidden;" src="http://www.screencast.com/users/normandinf/folders/Jing/media/86863818-b881-4dc1-a83d-b23f866aebde/embed" width="1376" frameborder="0" height="818" scrolling="no"></iframe> </object> Quote Link to comment
John Lokanis Posted March 31, 2011 Author Report Share Posted March 31, 2011 The way I've gone about this with OOP in the past is to use Events. Now, I don't know how well this solution would work for you, but basically, it's a framework by which each loop would subscribe. The framework is based on very simple code I put in the CR a while back: LVOOP Event Handler. Nice. I will have to test this option out. I would still like to do this with only queues and no events but maybe that is not necessary. Quote Link to comment
John Lokanis Posted April 1, 2011 Author Report Share Posted April 1, 2011 Ok, I think I solved it. Please take a look at the attached project and give me your thoughts. This seems like a nice clean way to implement cross messaging between multiple parallel processes using OOP to hide the queue refs. It also meets my requirements of ease of adding additional processes and messages without making changes to the parent classes. I especially like how the generic Send will adapt the the message type and automatically choose the proper channel based on the class hierarchy. I am sure I missed some details since this is my first attempt at OOP in LabVIEW. So, please let me know what I can/should do to improve this. thanks, -John MessageQ.zip Quote Link to comment
John Lokanis Posted April 2, 2011 Author Report Share Posted April 2, 2011 Added and improved the first version. Added code to release the queues and the DVR on exit. Added better error checking. Added error cases to all methods. Moved create method call to process section of code. This demonstrates that processes can be placed anywhere in the application, as long as the original MessageQ object is created first and an initial write is performed to generate the DVR. -John MessageQ.zip Quote Link to comment
ShaunR Posted April 2, 2011 Report Share Posted April 2, 2011 (edited) An for all the heathens out there. Here is the NON-LVOOP version (just changed the VIs) . Even I was shocked at 1.13MB for the classes implementation (the nonlvoop is only 108K) Edited April 2, 2011 by ShaunR Quote Link to comment
Francois Normandin Posted April 2, 2011 Report Share Posted April 2, 2011 Added and improved the first version. John, I could not test it because I lack LV2010 at home. Will take a peek at work next week. However, I can comment on the data structure based on the class directories. It seems your implementation requires you to create a new class for each new message type, and do that for each new channel you create. I don't think this is a good way to use the class hierarchy. Your channels and your message types are not separate entities as they should. Each classes is supposed to do one thing. In this case, either act on a message received, or send a message to specific channels. Not both. I might be completely off since I only comment on how your file structure is represented, but it looks like that if you were to create "Channel C", you'd have to also create new classes named: Channel_C_SendToA.lvclass Channel_C_SendToB.lvclass Channel_A_SendToC.lvclass Channel_B_SendToC.lvclass For Channel D: Channel_D_SendToA.lvclass Channel_D_SendToB.lvclass Channel_D_SentToC.lvclass Channel_A_SendToD.lvclass Channel_B_SendToD.lvclass Channel_C_SendToD.lvclass And it gets ever longer for each new channel you wish to create. To use wisely the OOP strenghts, you'll have to devise a way to have only one class to create if you add a new channel, ad one class to create if you add a new message type. I'm sure there's a way to do that using Queues. Like you would say, these classes need to be decoupled. Quote Link to comment
Daklu Posted April 3, 2011 Report Share Posted April 3, 2011 Ok, I think I solved it. Please take a look at the attached project and give me your thoughts. This seems like a nice clean way to implement cross messaging between multiple parallel processes using OOP to hide the queue refs. It also meets my requirements of ease of adding additional processes and messages without making changes to the parent classes. If it fits your needs then who are we to tell you it's wrong? But since you asked for our thoughts, here are a few: * It's an interesting way to take advantage of inheritance. My inner geek is celebrating. My inner pragmatist is sounding the alarm. * The unusual class hierarchy and odd dynamic dispatch terminal location makes it harder to understand what is happening. * Since you're using type checking to find the correct queue, my gut sense is that it is more complicated than it needs to be. I haven't really dug into it though. It seems your implementation requires you to create a new class for each new message type, and do that for each new channel you create. I don't think this is a good way to use the class hierarchy. Your channels and your message types are not separate entities as they should. Each classes is supposed to do one thing. In this case, either act on a message received, or send a message to specific channels. Not both. Agreed. I might be completely off since I only comment on how your file structure is represented, but it looks like that if you were to create "Channel C", you'd have to also create new classes named... That was my first thought too, but it's not correct in the general sense. Channel_A_SendToB.lvclass is message sent by the UI loop to the Channel A loop telling it to send a message to Channel B. It's just a way to test the ability to send messages between channels A and B. I don't think it's something you would do in a regular application. Quote Link to comment
Francois Normandin Posted April 3, 2011 Report Share Posted April 3, 2011 That was my first thought too, but it's not correct in the general sense. Channel_A_SendToB.lvclass is message sent by the UI loop to the Channel A loop telling it to send a message to Channel B. It's just a way to test the ability to send messages between channels A and B. I don't think it's something you would do in a regular application. OK, I was afraid I might be misinterpreting the disk hierarchy. Without looking at the code (no LV2010 at home), I was prone to make a wrong judgement there... Quote Link to comment
John Lokanis Posted April 3, 2011 Author Report Share Posted April 3, 2011 (edited) It seems your implementation requires you to create a new class for each new message type, and do that for each new channel you create. I don't think this is a good way to use the class hierarchy. Your channels and your message types are not separate entities as they should. Each classes is supposed to do one thing. In this case, either act on a message received, or send a message to specific channels. Not both. Here is how it works: The top level parent class (MessageQ) owns the messaging system. It stores the queues in it's class data. All channels must inherit from this class *and* override its methods. The channel class represents a single actor in your parallel architecture. It has the ability to receive messages from anyone within the application. It can also send messages to anyone within the application since it has access to the top level MessageQ class data. Additional channels can be created in your application by a simple Save As operation on an existing channel class. All messages (actions, functions, etc) are implemented as their own class. They must inherit from the channel class that executes them. If you have common code that more than one channel needs to call, you can easily wrap this code with a message class under each channel that needs it. The message classes only need to override the execute method of the parent. A message can have class data that is totally unique to itself. There is no need to ever modify the MessageQ class or the channel class when adding new messages in the future. You can also create new messages by doing a Save As on an existing message and then modifying it to meet the needs of the application. The Execute method of any message can send a message to any other channel (or even itself) by simply sending the destination message's object to the Send function. The developer does not even need to know what channel will execute the message. The class hierarchy figures that out automatically. So, to create an application using this architecture, you first determine how many parallel processes you need and create the channels. Then, as you add functionality, you create classes for each function. If you decide to add additional parallel processes later, simply duplicate an existing channel class and the process loop that uses it, then change the input object to the new class and you are done! Since the queue data is stored in the DVR in the top level MessageQ class, you can run multiple copies of your application in the same application instance without worry that they will interact. And, if you want other parts of your code to have access to all the channels, you simply pass them a copy of MessageQ object. And since this is a DVR, there is very little data copied. An for all the heathens out there. Here is the NON-LVOOP version (just changed the VIs) That is fine, but it fails to meet the design goals. You are using named queues. So, when you send a message, you must know the destination of the message and choose the right queue. Also, to add more messages, you must modify the execute function to add more cases. And, if you misspell the message command text, it will not work. So, it might be smaller, but it really is not the same architecture and does not have the advantages that LVOOP gives you. * The unusual class hierarchy and odd dynamic dispatch terminal location makes it harder to understand what is happening. * Since you're using type checking to find the correct queue, my gut sense is that it is more complicated than it needs to be. I haven't really dug into it though. The hierarchy is what enforces the message path. This is a technique I learned in the LVOOP class that NI teaches. I would like to have a better way to choose the queue, but this seems the most obvious. Also, since I don't anticipate this architecture being used with larges sets of channels, I don't think there is must performance penalty here. But, if you have a better solution, I am interested. Keep in mind my target for this architecture is user applications where speed is not an issue. It seems your implementation requires you to create a new class for each new message type, and do that for each new channel you create. I don't think this is a good way to use the class hierarchy. Your channels and your message types are not separate entities as they should. Each classes is supposed to do one thing. In this case, either act on a message received, or send a message to specific channels. Not both. Again, this is by design and is a technique I learned in the LVOOP class. Why is this a bad idea? When you say each class should do one thing, I thought that is what it was doing. Each channel class sends and receives messages. Each message class acts on a message. Isn't this basically what the factory pattern does? I suppose one improvement would be to make a single channel class and them implement some way to create channels with a method in that class. My current design uses the channel class itself as the differentiator between the channels. Perhaps I could make a friend class to hold the common components of the channel class. But, I still need a way to differentiate channels from each other so messages automatically go to the proper process. Anyways, my original goal was to implement an architecture replacement for the common producer-multiple consumer design I have used in the past. And to eliminate the need to pass a cluster of queue refs to each consumer (forcing me to update all of them if I add a new consumer and its queue ref). Also, to eliminate the need to have a type-def controlled enum and variant as the payload for each consumer to control execution. Meaning I would have to update the enum to add a message. Which in turn changes the type of the queue, which changes the cluster of queue refs which causes another recompile of the whole project practically... Not fun when using source control... (I'm sure AQ is going to chime in any moment and tell me why this is all a horrible idea... ) Edited April 3, 2011 by John Lokanis Quote Link to comment
ShaunR Posted April 4, 2011 Report Share Posted April 4, 2011 (edited) That is fine, but it fails to meet the design goals. Indeed. One design goal was "I want each loop to have its own child class". So it could never acheive that. You are using named queues. So, when you send a message, you must know the destination of the message and choose the right queue. As you must do with the class. The software doesn't know the developers' intentions, and must have a method for the developer to instruct it to send to the correct destination. The class does this by the developer laying down the appropriate class constant. The queue, by the developer laying down the appropriate string constant. Also, to add more messages, you must modify the execute function to add more cases. And, if you misspell the message command text, it will not work. That is a nature vs nurture type of argument. I would be saying single maintenance point, reduced VIs to debug, less VIs to implement., more code re-use. You would (probably) be saying more re-validation, more chance of breaking existing functions and more code re-use in 10 years time etc, . Granted spelling is a weakness (tell that to the JKI state-machine...lol) but the the upside is you can do other cool stuff like construct messages on the fly, automatic re-routing and scripting with minimal changes to the posted example. . So, it might be smaller, but it really is not the same architecture and does not have the advantages that LVOOP gives you. I'm not sure of what advantages you are talking about here (I don't see (m)any up-sides with LV OOP implementation for this - not the architectural implementation, the LabVIEW implementation mechanics). But I'm not here to derail your thread. Just add a bit for us non-POOP heathens. So we can probably leave it at you're designing a LVOOP messaging scheme and I've shown a functionally equivalent classical alternative for what you have so far. Edited April 4, 2011 by ShaunR Quote Link to comment
Francois Normandin Posted April 4, 2011 Report Share Posted April 4, 2011 Again, this is by design and is a technique I learned in the LVOOP class. Why is this a bad idea? When you say each class should do one thing, I thought that is what it was doing. Each channel class sends and receives messages. Each message class acts on a message. Isn't this basically what the factory pattern does? I suppose one improvement would be to make a single channel class and them implement some way to create channels with a method in that class. My current design uses the channel class itself as the differentiator between the channels. Perhaps I could make a friend class to hold the common components of the channel class. But, I still need a way to differentiate channels from each other so messages automatically go to the proper process. What I meant was that you should have one class per message type (10 message types = 10 classes), not one class per message type for each channel (10 message types, 5 channels = 50 classes). Like Daklu pointed out, this is not what you did but rather the additional classes I saw in your hierarchy were for testing the functionalities. I'm home today, so still can't have a look at the code. I'll refrain from further comments until I do. Quote Link to comment
John Lokanis Posted April 4, 2011 Author Report Share Posted April 4, 2011 What I meant was that you should have one class per message type (10 message types = 10 classes), not one class per message type for each channel (10 message types, 5 channels = 50 classes). I think where we are misunderstanding each other is in what a message is. My concept is a message is data and an action for a specific channel (process) to take. So, there would normally be no reason for two channels to use the same message, with the possible exception of a Stop or Quit command. And even in that case, each channel might need to have a special version of a Stop or Quit to do it's own cleanup. In my architecture, I have multiple parallel loops (processes) doing specific jobs. They each have their own unique set of actions. For example, one might be processing calls to a database. Another might be updating a UI display. Another might be launching plug-in VIs. And another might be receiving messages from the plugin VI and then messaging the other processes to write to the database or update the display or...etc...Perhaps if there were common commands, there would be a way to have those handled in the channel or MessageQ classes instead of each message. I will need to look into how that would work. I'm not sure of what advantages you are talking about here (I don't see (m)any up-sides with LV OOP implementation for this - not the architectural implementation, the LabVIEW implementation mechanics). One other thing I missed when looking at your non-LVOOP version: it does not support multiple versions of the architecture simultaneously. Since you are using named queues, if you have more than one copy of the architecture in memory at once, there will be cross-talk. I often (in the original non-LVOOP version of this architecture) need to have multiple instances of this code running in parallel and they must not know about each other. They also must be spawned from the same reentrant code base. Perhaps there is a way to eliminate the named queues in your code, but then we still have the issues of having to edit the VIs that support the architecture each time we want to extend it. So, on a multi-developer project, that can lead to some issues. In my version, I still need to edit the message classes to inherit from their proper channel, which means editing the project. I will need to look into a way around this so the messages become independent plug-ins to the project. Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.