09-11-2012 05:34 PM
Daklu wrote
- The parent Get DVR method is out of place and unnecessary. I believe it actually hinders understanding the code. I'd change the class properties to allow references and replace it with a Create DVR function. Much easier to understand and it removes the risk of accidentally using the Get DVR class output terminal.
- The repository stores all the data and only gives other components references. That's fine, but exposing the DVR itself to all the clients creates the risk that one of them might call Delete DVR and bring the whole system down, lock the DVR while performing extended calculations, or simply copy the object out of the DVR and not understand why the system isn't working correctly. You might consider wrapping each data object (i.e. the parent and child classes) in a reference class and giving that class to the clients.
If you can throw some code my way to illustrate what you mean, I'd be grateful.
Shane.
09-11-2012 08:51 PM - edited 09-11-2012 08:55 PM
I had something in mind like this. Instead of exposing the DVR and parent/child objects directly to the clients, you wrap them up in by ref classes and expose that to the clients. Your system has better protection and the api for the clients is simpler. If it were my code I might continue a little further and refactor the event generators out of the data classes (parent/child) and into the reference classes.
09-12-2012 02:26 AM - edited 09-12-2012 02:27 AM
I agree completely with placing the event generators int eh classes. This was a Q&D implementation to investigate how it would work.
Regarding passing the object itself out of the repository.......
I don't see the same behaviour here as I am looking for. I think a major part of the benefit of my original approach is lost.
In your example ------ Snip------- Forget what I just typed.
I think I've understood. What we have here is essentially decoupling my UI code from the actual data object. My Dynamic dispatched UI is located in the BR Parent (Blue Wire) and BR Child (Greenw ire) classes, right? This way the calls to the "Launch Editor" vi will launch the correct UI. But this object has a DVR of the actual data object as its private data, which can then atomically work on the data of the one and only copy of that object in memory. Thus synchronisation is guaranteed.
Hmm. I've heard of this approach before, but never really understood. I think the penny may have dropped.
I'll have to try it out later and see how I go.
Thanks
Shane.
PS BTW, what's that "Object Display Area" refnum you're sending to the objects if the Subpanel refnum isn't valid?
09-12-2012 02:34 AM
Daklu,
i just want to say thanks for helping me out of my idealogical corner so to speak. I'd become so happy with my solution that I was sure it was cool. But I think your solution (if indeed I have understood it correctly) is safer and more flexible (I can implement different UIs for the same data core) even if it requires a bit more maintenance (Wrapper Classes).
So this is just an official sincere thanks for being a huge help for me and others.
Shane.
09-12-2012 11:28 AM
@Intaris wrote:
I think I've understood. What we have here is essentially decoupling my UI code from the actual data object. My Dynamic dispatched UI is located in the BR Parent (Blue Wire) and BR Child (Greenw ire) classes, right?
You're on the right track, but at the risk of being pedantic let me respond to your question precisely.
In the existing design there's no clear distinction between UI code and non-UI code. The data objects (parent and child) have UI elements built into them by virtue of the Object Editor and Object Viewer Subpanel vis, and the top level vi has UI elements by hosting the subpanel frame. So no, this hasn't improved UI decoupling. The top level vi is better decoupled from the data objects, but not the UI in general.
Yes, the ByRef classes contain the dynamic dispatch Launch Editor method used by the client software. Clients can use the parent's Launch Editor method in their code and be assured the correct editor will be activated for the current object at runtime.
----
As a proof of concept I think what you've done is pretty cool and if it works for you there's no reason not to use it. Instinctively, if I were implementing this design in a production system, I'd probably look for ways to completely remove the UI elements from the data objects. My initial thought is to have the repository store and distribute by ref data objects and let the client code would wrap it in a class implementing the Editor and Viewer for the UI. (Given the freedom I still prefer messaging-based systems over reference-based systems, but that's for another thread.)
There are other related issues with this exact implementation I would address prior to production release too. (I point them out for the benefit of other readers. I'm sure these are things you've considered.) For example, currently the subpanel reference is assigned in the Repository. That's a mistake; the Repository shouldn't know anything about the UI side of the application. That operation should be done in client code. (In the code I posted, that would be just before entering the Monitor loop.) As I said, I'd fix that by moving the Editor and Viewer into a class on the client side, but you could also just create a Set Subpanel method in the reference classes.
Also, I don't really like how Launch Editor and Refresh Object Viewer have fundamentally different ways in which the client code needs to use them. Launch Editor fires off a new continuous vi while Refresh Object Viewer needs to be in a loop. The inconsistency makes the api for the ByRef classes less intuitive. I'd look for ways to make them more consistent, or at least split the Editor and Viewer functionality into separate objects.
Finally, the mix of user events and ByRef data objects causes unusual behavior when one or more of the references is invalid. Try running the example code with Parent and Valid Numeric Event set to false, then try changing the numeric values in the editors. I don't know if that behavior is intentional on your part, but it strikes me as something that could cause ongoing headaches. Enapsulating the references so clients don't have access to them will probably alleviate most or all of the concerns there, but it is something to keep in mind.
@Intaris wrote:
PS BTW, what's that "Object Display Area" refnum you're sending to the objects if the Subpanel refnum isn't valid?
Erm... it's part of your code. I didn't change that.
It's the reference to the subpanel frame, and it's sent when "Valid subpanel refnum" is True.
@Intaris wrote:
So this is just an official sincere thanks for being a huge help for me and others.
You're welcome. There's no way I can directly help as many people as you, Ben, Christian, and the other Champions do. (And you all deserve mad kudos for the work you do.) I'm content sitting behind the stage trying to change the script for the star actors. ![]()
09-12-2012 11:35 AM
BTW, I'm curious about your use case for this design. You have multiple UI elements running in parallel and displaying/editing the same underlying data. That implies some sort of distributed system, but DVRs and user events don't work across OS process boundaries. Why do you need multiple UI's for editing the same data in a single application?
09-13-2012 02:46 AM - edited 09-13-2012 02:48 AM
I just wrote a large reply to this mail but the "post" button swallowed it hole and it pasted instead a copy of the post I was replying to. Grrrr. Lithium.
09-13-2012 02:56 AM
To the answer ( for the second time unfortunately).
We have a sysstem running on a host PC (single OS, single PC) which communicates with a RT and FPGA system. My synchronisation worries are linked only to the host software running on the PC (Windows).
We have a list of system-relevant values which need to be distributed among several parallel processes which are displaying / allowing user input in different ways depending on what the user is currently trying to do. We need to keep these synchronised between the repository (which in turn synchronises itself with the RT and FPGA code) and all of the viewers open at the time.
Using DVRs allow the synchronisation between repository and a single client to be easy. Synchronising multiple clients ont he other hand requires an extra logic step. This I have tried to solve with user events. Good or bad is open to discussion, but I don't want to go near the polling route for keeping the clients up to date.
When I mentioned decoupling the UI from the core object I was referring to your "wrapper" classes which could actually also house the UI code, thus decoupling the UI from the core logic (which is certainly NOT the case in my code at the moment). This is a big benefit which was not explicitly mentioned in your original reply but which I quickly realised would be a further improvement (I think).
Setting the subpanel reference int he repository is a left-over from another idea I had which would allow the user to debug the code more easily. The repository itself can display the object contents by placing a subpanel on its FP and showing the data for the input and putput data instead of a simply object icon. I think this would help initial debugging a lot but for normal operation, the subpanel is and must be part of the client (because this is where the subpanel will actually be).
"Object display area". I have no idea where this comes from, I originally wired a "Not a Refnum" on mymachine so this was automagically changed somewhere down the line. Weird.
Shane.
09-13-2012 08:10 AM
@Daklu wrote:
Finally, the mix of user events and ByRef data objects causes unusual behavior when one or more of the references is invalid. Try running the example code with Parent and Valid Numeric Event set to false, then try changing the numeric values in the editors. I don't know if that behavior is intentional on your part, but it strikes me as something that could cause ongoing headaches. Enapsulating the references so clients don't have access to them will probably alleviate most or all of the concerns there, but it is something to keep in mind.
The events between the children and others is an ugly neccessity at the moment.
The repository has no automatic possibility for checking when a child changes data of the object. I need SOME kind of signalling so that the change is signalled back to the repository which can then signal the children to update their indicators and controls. You can think in terms of Actors and messages if you like, the effect is the same.
Encapsulating the events within the objects makes the problem less severs but the events need to be accessed SOMEWHERE otherwise they won't work. Any other ideas would be welcomed.
Shane.
09-13-2012 02:02 PM - edited 09-13-2012 02:03 PM
@Intaris wrote:
We have a list of system-relevant values which need to be distributed among several parallel processes which are displaying / allowing user input in different ways
Ahh... so the whole thing is part of a larger UI component. I can see how that would affect some of your design decisions. In that case I would be less concerned about separating UI code from functional code. (I'd probably still do it, just with less concern.
)
@Intaris wrote:
Good or bad is open to discussion, but I don't want to go near the polling route for keeping the clients up to date.
As long as your code satisfies your requirements "good" and "bad" are very subjective, so I try to avoid thinking about code quality in those terms. I like "sustainability," which is a catch all for things like readability, flexibility, testability, debugability, robustness, etc. Still subjective, but more specific.
@Intaris wrote:
Using DVRs allow the synchronisation between repository and a single client to be easy. Synchronising multiple clients ont he other hand requires an extra logic step. This I have tried to solve with user events.
Yep, since DVRs don't have a built-in value changed event you have to roll your own. I tend to avoid byref data objects (BRDO)
and I'm undecided on whether or not I'd use event capable byref data objects. There's nothing obviously wrong with them, but it sure feels like updating the value then sending out a message announcing the change defeats the purpose of having a BRDO in the first place. Functionally it's exactly the same as keeping the data object in the repository and having the repository send new copies to the clients when it receives an update request. I can understanding using a BRDO if the object consumes a lot of memory or is expensive to copy, but barring that....
Scratch that. I don't like them--at least not this specific implementation of them. My issue is the timing. There's a delay between the DVR being changed and the client receiving the value changed event. That means every time a value is updated the client will pass through a state where it thinks the old values are still valid but has access to the new values. That has race condition written all over it. Not all code will be succeptable to the race condition, but knowing Murphy it will happen Friday afternoon right before your two week vacation.
It should be possible to eliminate that risk by removing all the getter methods and only allow clients to receive updates via a queue or user event. I haven't thought through that all the way though. At that point you'd have something very similar to a pure by-value messaging based system.