Steen Schmidt Posted July 11, 2011 Author Report Share Posted July 11, 2011 I've been on vacation and I haven't looked at the VIRegister library at all, but I did want to share how our team stops loops. All of our code has something like the following architecture, and all loops are stopped by destroying a reference to a queue or notifier, which is a pattern we call "scuttling". It doesn't matter whether the wait functions have timeouts or not, it works immediately and all you have to do is filter out the error (error 1 or error 1122) that it throws. Usually the code is split into many VIs, but as long as you make sure that any queue or notifier reference is eventually destroyed (we try always to do that at in the same VI as the creation), then all the loops will stop. That's a very robust way of sharing data, I use that alot too. In fact I have a toolset for stuff like this, called ModuleControl: I call these queues "command lines", since they convey commands (which can bring data with them, are timestamped and so on). The read-function provide a Command line destroyed? output that basically wraps the queue destroyed event as you use. This part is actually quite tricky when handling more than one queue at a time, it involves many of the issues Stephen mentions in his Alpha->Beta prioritized communication posts. A bit of the VI documentation for ModuleControl: Cheers, Steen Quote Link to comment
Aristos Queue Posted July 12, 2011 Report Share Posted July 12, 2011 Hmmm. If that is true. How is it reconciled with the events of front panel controls which surely (neck stretched far) must be in the UI thread. I could understand "User Events" being able to run in anything, but if bundled with a heap of front panel events; is it still true? As I said before: UI events are generated in the UI thread, obviously, but they're handled at the Event Structure. Quote Link to comment
Daklu Posted July 12, 2011 Report Share Posted July 12, 2011 I don't think we disagree on much here. ...but I don't think it's all as black and white as you make it sound. Mmm... didn't mean to imply that it was black and white, or that my opinion is the only valid opinion. So regarding references; the issue seems to boil down to if the reference is public or private. For the most part, yes. Limiting the scope of components and controlling the interactions between them is at the heart of how we bring order to the complexities of a large system. Globally available data violates the order we're trying to establish and, IMO, is best avoided when possible. I don't have any issue with references in and of themselves. I just prefer to avoid them if possible as I think it can easily add uncertainty to the system. Do I go to great lengths to avoid them? Not really... but I do try to use patterns and architectures where they aren't necessary. LabVIEW is evolving into a language more and more devoid of wires; Perhaps... but it doesn't mean we shouldn't use wires when we can. Faced with the choice of implementing a solution with wires versus a solution without wires, I'll choose the solution with wires unless there are extenuating circumstances that make the wire-free option the better technical solution. What other people can or can't comprehend and what they might make a mess of, isn't my concern. I confess, your apparent lack of concern for code maintainability confuses me. Am I misunderstanding what you're trying to say? Do you work in an environment where you and only you ever look at your code? Therefore, the difference in my mind between a named and an unnamed queue is convenience. In my mind, the main difference between them is readability and confidence. When I'm looking through new code there's far more work involved in understanding and verifying the behavior of a named queue versus an unnamed queue. With a named queue I have to search for the queue name to find all the vis that use it, trace each vi's calling tree back to figure out where it fits in the calling tree, and then figure out how all the components relate to each other. I have to put the puzzle together using a random assortment of apparently unrelated pieces. With an unnamed queue I can look at the block diagram and see which other components use the queue because it's connected via a wire. I start with the puzzle completed and can examine each section as interest or need dictate. For me to gain confidence the components are going to work together correctly, I have to trace through the code and make sure unexpected things can't happen due to all these asynchronous loops obtaining and releasing the queue on their own schedule. I have to figure out all the possible timing variations that the parallel loops can execute with. I've seen code by very good developers where a parallel loop may obtain a named queue, send a message, and release the queue before the message receiving loop has had a chance to "permanently" obtain the named queue. They built race conditions into their code without realizing it. These aren't LV newbies; they're people who have made a living with Labview for a decade or more. They just wanted to take advantage of the "convenience" of named queues to reduce the number of wires on the screen and have a cleaner block diagram. (FYI, I succumbed to the "convenient and clean" siren song for a while too.) Please don't take that as a dig on you--I don't mean it that way. There are things that can be done to reduce or remove the risk of named queue race conditions. From a practical standpoint I think named vs unnamed queues is largely a matter of style. (In the situations I saw I believe it is unlikely the race condition would have been an issue during execution.) I personally don't like them for the reasons I explained earlier. It's harder for me to use named queues to discover the relationships between loops, and even if the race condition is unlikely to ever occur during execution, I still have to go through the work and convince myself that's the case. Unnamed queues might take a few minutes longer to implement but remove both of those issues. It's a worthwhile trade, IMO. When I keep that name secret, no one else is going to change my data... Ahh.... security through obscurity. We all know how well that works. (Joking. I realize the risk of somebody trying to hack into your application via a named queue is probably very, very, low.) There isn't any replacement for sharing data (by reference) when you want to share data (a common signal). I can't use by-value for this. Maybe this is a stupid question, but can you explain exactly what you mean by sharing data? Is it data multiple components both read from and write to? Do you consider data created by one component and consumed by another "shared?" I ask only because I'm not sure I agree with your assertion references are required to share data, but my disagreement hinges on what you consider shared data. But application development is always about compromise... There are many ways to do it wrong, but I still believe there are also many ways do it right. Agree 100%. I've mentioned it before on the forums, but it's never my intent to tell someone their implementation is wrong. (Unless, of course, it doesn't meet the stated objectives.) All design decisions carry a set of consequences with them. Experienced developers understand the consequences of the decisions and make choices where the consequences are most closely aligned with the project goals. Your goals are different than my goals, so your "right" decision is going to be different than my "right" decision. I share my implementations as a way to explore the consequences of the different design decisions we are faced with so we all can be better informed. It's not my intent to preach the 'one true path.' Quote Link to comment
Aristos Queue Posted July 12, 2011 Report Share Posted July 12, 2011 LabVIEW is evolving into a language more and more devoid of wires; I strongly hope not, because the more it moves away from wires the less optimal and the more failure prone applications written in the language become. First you bundled stuff together, then we magically wafted data off into the FP terminals of dynamically dispatched VIs, then we got events, and now we have all sorts of wire-less variables like Shared Variables. Your time table here is a bit messed up, but I understand your point. I don't think that bundling and dynamic dispatch belong in your list, however. There's a difference between representing the wire in an alternate form (bundle is several wires all routed together; dynamic dispatch is equivalent to a case structure around a number of subVI calls) and abandoning the dataflow syntax entirely (I'm looking at you, Shared Variable). And, more important than all of that... my whole reason for asking for this thread to start in the first place... is that I think any drift away from wires is unnecessary, but it is seen by many as necessary because of the architectures we favor today. Those architectures are the problem that I'm wanting us to address. Whether or not you can successfully use variables and DVRs is irrelevant to this conversation, IMHO, because I believe you can use wires equally as well or better if we structure them correctly and I know that the compilers, debuggers, and analyzers can definitely handle wires better, so it behooves us to prevent as much drift as possible. 1 Quote Link to comment
Steen Schmidt Posted July 12, 2011 Author Report Share Posted July 12, 2011 Perhaps... but it doesn't mean we shouldn't use wires when we can. Faced with the choice of implementing a solution with wires versus a solution without wires, I'll choose the solution with wires unless there are extenuating circumstances that make the wire-free option the better technical solution. I'm starting to get the gist of your message. It's something I need to give some more thought, but I can easily see the safety of using wires versus references. I've always been aware of the pitfalls of references of course, but I may not have assigned sufficient weight to the dangers of such architectures. It must be a natural step in my development as a programmer, which incidently fulfills my wish from my initial post that I'm hoping to learn something new here . In that case I'll probably start asking questions about the alternatives to references with offspring in concrete use cases then. But this isn't a point of view widely adopted I'd say? Not even inside NI it seems. That probably ought to change. What other people can or can't comprehend and what they might make a mess of, isn't my concern. I confess, your apparent lack of concern for code maintainability confuses me. Am I misunderstanding what you're trying to say? Do you work in an environment where you and only you ever look at your code? No, you're right - it was a very egotistical statement from me. I'm just kind of used to customers making a mess of even the simplest of things sometimes, that I've grown to expect people to have difficulty understanding the solutions I (and my colleagues) provide. Not because the solutions aren't maintainable or that they lack consistent coding style, but simply because I use more advanced functionality than the case structure basically. But it was a rude generalization from bad habit - I apologize. I do indeed go to great lengths to achieve the most solid and maintainable architecture, and when I on workshops demo my code the floor usually have no difficulty understanding what goes on. Therefore, the difference in my mind between a named and an unnamed queue is convenience. In my mind, the main difference between them is readability and confidence. When I'm looking through new code there's far more work involved in understanding and verifying the behavior of a named queue versus an unnamed queue. I agree completely. This is also the case with events and FGs and the like. I just need to get my head around to the fact that so many use these methods without raising the same questions (JKI for instance). For me to gain confidence the components are going to work together correctly, I have to trace through the code and make sure unexpected things can't happen due to all these asynchronous loops obtaining and releasing the queue on their own schedule. I have to figure out all the possible timing variations that the parallel loops can execute with. I've seen code by very good developers where a parallel loop may obtain a named queue, send a message, and release the queue before the message receiving loop has had a chance to "permanently" obtain the named queue. They built race conditions into their code without realizing it. These aren't LV newbies; they're people who have made a living with Labview for a decade or more. They just wanted to take advantage of the "convenience" of named queues to reduce the number of wires on the screen and have a cleaner block diagram. (FYI, I succumbed to the "convenient and clean" siren song for a while too.) I did just that in VIRegister v1.0. It could release a named queue reference before any reader got hold of it due to an unforseen use case . Case proven. When I keep that name secret, no one else is going to change my data... Ahh.... security through obscurity. We all know how well that works. (Joking. I realize the risk of somebody trying to hack into your application via a named queue is probably very, very, low.) Sure, for secure applications that path won't work. But there are so many ways to jam a wedge into a (standard run-of-the-mill) LabVIEW application that it won't matter much from a security standpoint. I'm addressing the mistakes that could happen if naming a "globally referenced variable" something common. But my focus is misplaced there - I should concentrate at least as much on the inherent negative exploitability of referenced data. I've just seen it as pretty easy, which is kind of dangerous (and I have noticed the first sentence in your signature ). There isn't any replacement for sharing data (by reference) when you want to share data (a common signal). I can't use by-value for this. Maybe this is a stupid question, but can you explain exactly what you mean by sharing data? Is it data multiple components both read from and write to? Do you consider data created by one component and consumed by another "shared?" I ask only because I'm not sure I agree with your assertion references are required to share data, but my disagreement hinges on what you consider shared data. Sure. I mean shared for instance when multiple readers need to react to the same "signal" (a Boolean stop-signal is probably the simplest example). If you wire that data by-value (forking the wire) from the (usually single) source to multiple readers, they'll read a constant and stale value. The readers obviously can't detect if data in another wire changes its value. I may be ignorant here, but I'd solve that with a queue for instance (or a notifier or event), but in all these cases, even though we are for sure wiring to all readers, what we're wiring is still a reference to the same copy of data. Then the question of how we obtain that reference remains; is that by wire (e.g. unnamed queue) or referenced in itself (e.g. named queue). It's probably this I can't see how to solve without using referenced data at all? A similar use case I have a hard time finding a "wired" solution for is sending data to a dynamically dispatched VI without using the FP terminals (Set Control Value). The reason for not using the FP terminals is that it's very fragile (a constant string must match the terminal label), and because it won't work too well in built applications (you need to include the FP here) nor on LV Real-Time (the FP is a bad communication interface on this platform except for FPGA). Even if we say unnamed queues are ok, you still need to deliver at least that reference to the dispatched VI somehow. I've mentioned it before on the forums, but it's never my intent to tell someone their implementation is wrong. (Unless, of course, it doesn't meet the stated objectives.) All design decisions carry a set of consequences with them. Experienced developers understand the consequences of the decisions and make choices where the consequences are most closely aligned with the project goals. Your goals are different than my goals, so your "right" decision is going to be different than my "right" decision. I share my implementations as a way to explore the consequences of the different design decisions we are faced with so we all can be better informed. It's not my intent to preach the 'one true path.' Don't worry about hurting my feelings, you have very far to go yet at that - not that I find you offensive at all . When I look into my past as a programmer (in this context) I can clearly see the learning curve I've climbed. And I didn't expect to be done with that just yet. I'm reading intently because I know there's something new here to learn for me. And it's a privilege to be able to participate in forums like this, even though it can be hard to find the time. Quote Link to comment
viSci Posted July 12, 2011 Report Share Posted July 12, 2011 If the finest minds at NI think that NSV's are abhorrent, then maybe they could recommend an alternative for cRIO based SCADA applications that need features such as OPC or EPICS binding, citadel data logging, PSP capability (efficient NSV-NSV and NSV-control binding, NSV events), IEEE 1588 timestamping, DSM capability, etc. (and no most of us do not have the time to invent a 'tcpip-link') Is it possible to develop a NSV best practices to minimize race conditions and wide open global accessibility. Perhaps the use of project scoping, NSV class wrappers, etc. I have had good success with NSV's in cRIO based SCADA applications. I did go through a severe learning/tearing my hair out period, but now NSV's (1000's of transactions/s on a cRIO) appear to be reliable. BTW, I never have any sort of issues like spontaneous NSV un-deployment. However I do not use NSV's as a streaming conduit, my only use of buffering is for a command channel with reply notification based on NSV events. Quote Link to comment
Daklu Posted July 12, 2011 Report Share Posted July 12, 2011 Quick response--short on time. Will add more later. But this isn't a point of view widely adopted I'd say? Not even inside NI it seems. That probably ought to change. No, it is not a point of view that is widely adopted, at least among advanced developers. (It's okay... I'm used to holding a minority viewpoint. ) We're tech geeks and we like to play with the latest toys: DVRs, shared variables, etc. It's intellectually stimulating, fun, and if I'm being completely honest I have to admit it strokes our ego to some extent. (I know I have an inherent desire for intellectual stimulation that comes out as a tendency to make things more complicated than they need to be because it's more interesting. I'm contantly battling against that desire, trying to make the "best" decision instead of the "most interesting" decision. Sometimes I win... sometimes I don't.) I can't speak to how people think inside NI; I haven't talked to enough of them. I know Stephen has been discouraging references for quite some time. It was a combination of his comments and the things I discovered while developing the Interface framework that encouraged me to minimize references and "embrace the wire." Having spent the last several years learning and discovering how to do that, I do believe my programs are less buggy and more sustainable than they would be otherwise. The downside? My architectures seem overly complex (especially to standard QSM developers) and I always encounter a boatload of "Why didn't you just...?" questions. [Edit - Also, my experience is almost completely in desktop applications and my comments reflect that point of view. I have not yet had the opportunity to work on cRIO or any real-time targets.] Quote Link to comment
Aristos Queue Posted July 13, 2011 Report Share Posted July 13, 2011 But this isn't a point of view widely adopted I'd say? Not even inside NI it seems. That probably ought to change. It does change, regularly. But then we bring in a new crop of interns and new hires, and the process of untraining them from their C++/C#/JAVA faith and indoctrinating them with dataflow begins anew. Some never learn; others do. :-) Quote Link to comment
Cat Posted July 14, 2011 Report Share Posted July 14, 2011 In my mind, the main difference between them is readability and confidence. When I'm looking through new code there's far more work involved in understanding and verifying the behavior of a named queue versus an unnamed queue. With a named queue I have to search for the queue name to find all the vis that use it, trace each vi's calling tree back to figure out where it fits in the calling tree, and then figure out how all the components relate to each other. I have to put the puzzle together using a random assortment of apparently unrelated pieces. With an unnamed queue I can look at the block diagram and see which other components use the queue because it's connected via a wire. I start with the puzzle completed and can examine each section as interest or need dictate. But what about the case of dynamically called code? An unnamed queue isn't going to work there. Which makes me wonder... if you don't use named queues because you can't trace a wire to/from them, then maybe you don't use dynamically called code either? Yes, I'm kidding, but the points in your argument above can be applied to either. Quote Link to comment
Tim_S Posted July 14, 2011 Report Share Posted July 14, 2011 But what about the case of dynamically called code? An unnamed queue isn't going to work there. Named queues can be used if you set a front panel control, passing using a call by reference, populate a FGV, populate a global, etc... Tim Quote Link to comment
Cat Posted July 14, 2011 Report Share Posted July 14, 2011 Named queues can be used if you set a front panel control, passing using a call by reference, populate a FGV, populate a global, etc... I know. I was asking Daklu about a specific use case where unnamed queues wouldn't work. I use named queues in dynamically called code quite often. Quote Link to comment
Steen Schmidt Posted July 14, 2011 Author Report Share Posted July 14, 2011 (edited) Named queues can be used if you set a front panel control, passing using a call by reference, populate a FGV, populate a global, etc... Tim You must mean unnamed queues... But using FGVs and globals are using references, so therefore deemed "unworthy" apparently. Call by reference and using the FP specifically isn't a good option on Real-Time. I'm wondering about dynamic code myself in this context, hence my earlier question regarding that. /Steen Edited July 14, 2011 by Steen Schmidt Quote Link to comment
Cat Posted July 14, 2011 Report Share Posted July 14, 2011 You must mean unnamed queues... But using FGVs and globals are using references, so therefore deemed "unworthy" apparently. Call by reference and using the FP specifically isn't a good option on Real-Time. I'm wondering about dynamic code myself in this context, hence my earlier question regarding that. Yeah, what you said. I've done all that, too, and keep coming back to named queues and FGVs when passing data between dynamically called code. And it was actually your earlier comment I wanted Daklu to follow up on... Quote Link to comment
Tim_S Posted July 14, 2011 Report Share Posted July 14, 2011 You must mean unnamed queues... Yes, typo on my part. Quote Link to comment
Daklu Posted July 14, 2011 Report Share Posted July 14, 2011 Personally I haven't run into situations in my code where the only solution is to use a named queue. I'll mention it every so often as part of my standard disclaimer, but I haven't had the opportunity to work on any real time systems yet and I know there are different constraints and concerns in that environment. (Don't know what they are though... Steen's comment about the FP being a bad communication interface on RT interests me.) My experience is almost completely in the desktop application space and my comments and ideas are directed at that platform. The one situation I've seen where a named queue was required is (IIRC) in AQ's Actor Framework. I think it had to do with the launcher, but my memory is fuzzy. Which makes me wonder... if you don't use named queues because you can't trace a wire to/from them, then maybe you don't use dynamically called code either? Actually, I rarely use dynamically called code anymore. I don't know if that's because my projects don't require it, or my dev approach leads to different solutions, or some other unknown reason. Dynamically called code is harder to understand and debug, so I tend to avoid it unless it's clearly the best option. When is it the best option? I dunno. My working theory is it is the best option when I need to have a large or unknown number of identical parallel processes executing. True story: ~8 months ago I was showing a coworker some of my code so he could tie into it. I started explaining the part where I launched an active object (essentially a dynamically called object) and he kind of gave a condescending chuckle and said, "you're calling it dynamically?" A few minutes later he left. Yeah, initially I was a bit irritated at his attitude. But it did prompt me to reexamine my code and I realized he was right--in this situation there was no need for dynamic instantiation. I had gotten too deep into the weeds and had lost sight of the bigger picture, and as a result spent a lot of time implementing unnecessary complexity. He came back the next day and apologized for his chuckle. I tried to explain I was glad he did it, but I'm not sure my message got across. (On the off chance he's reading this, thanks J, it was an important lesson for me.) I was asking Daklu about a specific use case where unnamed queues wouldn't work. Like Tim, I'd pass the input and output queue references in through a fp control. Can you explain more about the use case where unnamed queues won't work? Are you referring to RT systems? If I do need to use a named queue, such as for an Actor Object, I'll use it only to cross that boundary where it's needed. An ideal solution would use the named queue just long enough to pass in a permanent unnamed queue reference, then the named queue would be released. A less-ideal-but-still-reasonable-for-some-people solution would be to use the named queue permanently, but only obtain it in the two locations where the named feature is explicitly needed. In other words, once the dynamic vi has the named queue reference, always treat it as an unnamed queue. At least when someone searches for the queue name they'll only come up with two hits. Sure. I mean shared for instance when multiple readers need to react to the same "signal" (a Boolean stop-signal is probably the simplest example)... It's probably this I can't see how to solve without using referenced data at all? I make a distinction between a reference and reference data. Queues, notifiers, etc., are examples of references. FGs, globals, DVRs, etc., are examples of reference data. References are necessary to pass data between parallel processes. Reference data is not. Why not use copies? In the case of a stop signal, rather than using a reference boolean that is universally read by all the loops, each loop receives their own copy of a Stop message. Is reference data sometimes needed? Yep. In my current app I'm using a global to pass high capacity video from one component to another to meet performance requirements. We might also need to use reference data if we're working under very tight memory constraints. My rule of thumb is to use reference data because it's necessary, not because it's convenient. Another true story: In my current project I have a class encapsulating data loaded from a config file on disk. Each config file contains thousands of lines of .csv data. There are two unrelated components that need to use this information. I struggled for a bit trying to figure out how to make the information in the object available to both components. Good spot for reference data, right? In the end I decided to just create a copy of the object and send one copy to each component. The additional memory used is insignificant in this case and the code is much simpler. If memory use was an issue or they needed to modify the object's data, I would have put the config file object in a loop accessible to both components and used messaging to set/get data. --------------------- Incidentally, the master/slave organization I described above doesn't necessarily work in all situations. Kugr and I have been talking about systems designed around dynamically launched observer and observable objects. Each observer receives messages from one or more observables. There are a couple options for implementing observer-type behavior into this system, but I'm not sure which is best or, in fact, if any of them are any good. Quote Link to comment
Aristos Queue Posted July 15, 2011 Report Share Posted July 15, 2011 But what about the case of dynamically called code? An unnamed queue isn't going to work there. Allow me to introduce you to LabVIEW 2011. It... huh? Oh... I can't talk about that yet? Seventeen more days? Well, ok... but you're not going to keep a secret like the Asynch Call By Reference node quiet for ever. ... anyway, there's help coming.Personally I haven't run into situations in my code where the only solution is to use a named queue. I have. In the Actor Framework that I posted as part of my NI Week 2010 presentation. That's why we've made changes to the framework using LV 2011, where we have the Asynch *mmmffph mmmph oooph argh* No, really, I'll stop talking about it. I promise. Really. Just stop the beatings. I can wait 17 days... Quote Link to comment
Steen Schmidt Posted July 15, 2011 Author Report Share Posted July 15, 2011 Allow me to introduce you to LabVIEW 2011. It... huh? Oh... I can't talk about that yet? Seventeen more days? Well, ok... but you're not going to keep a secret like the Asynch Call By Reference node quiet for ever. ... anyway, there's help coming. Heh Now you mention it... In the context of Real-Time; does this (these) nodes use the FP for data transfer or is the con pane on the node just an interface directly to the terminals on the BD of the receiver? I'm basically asking if input data winds up in the user interface thread and so must cross in the transfer buffer, or if it is injected directly into the execution thread without forcing a thread switch? The pre-11 Synchronous node forces an initial swap to the user interface thread on the receiver end to deliver input data as I understand it... /Steen Quote Link to comment
Aristos Queue Posted July 16, 2011 Report Share Posted July 16, 2011 The pre-11 Synchronous node forces an initial swap to the user interface thread on the receiver end to deliver input data as I understand it... No, it doesn't. And neither does the... Quote Link to comment
Steen Schmidt Posted July 16, 2011 Author Report Share Posted July 16, 2011 No, it doesn't. And neither does the... Thx. Quote Link to comment
Cat Posted July 18, 2011 Report Share Posted July 18, 2011 I was asking Daklu about a specific use case where unnamed queues wouldn't work. Like Tim, I'd pass the input and output queue references in through a fp control. Can you explain more about the use case where unnamed queues won't work? Are you referring to RT systems? Some text got lost in there somwhere. I was still talking about dynamically called code. I agree, if I can pass a wire in, that's what I'm going to do. Allow me to introduce you to LabVIEW 2011. It... huh? Oh... I can't talk about that yet? Seventeen more days? Well, ok... but you're not going to keep a secret like the Asynch Call By Reference node quiet for ever. ... anyway, there's help coming. Tease. Quote Link to comment
Cat Posted July 18, 2011 Report Share Posted July 18, 2011 My working theory is [dynamically called code] is the best option when I need to have a large or unknown number of identical parallel processes executing. I agree and that's often where I use it. There are many other benefits to using a plug-in architecture in general, but I don't want to get too far off topic. I've also had this thought running around in the back of my head that if I call memory-intensive routines dynamically, I should actually get that memory back when they're done executing, as opposed to calling them statically and having LV hang onto memory long after it doesn't need to anymore. But I haven't had a chance to play with that, and it's definitely a discussion for another thread, anyway... Quote Link to comment
crelf Posted July 18, 2011 Report Share Posted July 18, 2011 Moderator's Note: mballa's Message Routing Architecture post was moved to its own thread here. 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.