Jump to content

Decouple UI and application core - need advice


Recommended Posts

I have to decouple the UI part of a medium size application (approx. 2000 VIs) from its core.

When we created the application it was a simple state machine.
Later we realized that some operations must be asynchronous
so we added an event-based control loop that controls the state machine
via user events. But it was still a single VI and over the years it grew bigger...
Now I decided to move the state machine (approx. 70 states) into a subVI but since
there never was a separation between the UI part and the non-UI part I now
need a way to access the UI controls (approx. 40 controls) from the state machine.


Currently I see the following options:

1) User events from/to the state machine. This seems to be a quite clean
approach but means a lot of work.

2) Use something like Actor Framework. Probably the most clean approach but
even more work.

3) Get/Set Control Values by Index. I prefer that over Get/Set Control Value
because I often have to read/write several controls at once. I can easily
encapsulate this in a nice class that maps my control names to indices.

4) Put the control refnums into a cluster and pass that to the state machine subVI.
This is the approach I dislike the most. I already decided not to do it that way.

Currently I like 3) best but I have mixed feelings about it:
- It seems odd to use control access to separate application parts. Maybe that's just me but it feels wrong...
- I have controls of which I have to access other properties than the value so I definitely need events here.
- Searching here on LAVA brought nothing about Get/Set Control Values by Index which disturbs me a little bit.

Do you have any any advice for me? Do I miss something important?

Thanks in advance,

candidus
 

Link to comment

I like OO - so bear that in mind with my answer!

Ideally you don't want your state machine to know how UI updates are done - it doesn't care, it just needs it done. So I would consider creating a UI class or multiple UI classes for the different parts of the UI with update methods which your state machine can call. Inside of their you can use any of the methods and chop and change between them.

Within there my temptation would be to queue them as messages or user events to a separate UI loop but once the separation is done it is easy to try different techniques.

Link to comment

The way I handle this is the UI event that is generated when the user interacts with stuff, will generate a user event for some work to be done.  Then when I want that work to be done anywhere else in the application, all I need to do is generate that same user event.  Now the code that my sequence calls, is the same code that my UI calls.  This makes debugging sequence issues pretty easy since I can just manually perform the same steps as what the sequence would be doing, by performing the actions with the UI, which would generate the same user events as the sequence would.  It is a lot of upfront work, and does feel like an unnecessary layer of abstraction, until changes in how something behaves is needed, and now I no longer need to update both the code in my UI event case, and the code in another case.

Link to comment

This makes sense on the surface but I don't understand the real impetus for the change -- you kind of jumped from "its big and complex" to "here are solutions I came up with for the problem". Do you need to be able to reuse your core state machine inside of another application, or share behavior between implementations? Most of your solutions sound like they are still tightly coupled to a specific UI (eg get set control values), you're just adding an additional layer of code.

 

More in reply to the other posts, since as I said I dont totally understand the problem you're trying to solve: the way I do most UIs is to have a single loop with the following features:

  • Event loop
  • async call references for any long running tasks w/dynamic event for result
    • this is something drjd mentioned using in his messenger library as well
    • I had made something which somewhat generalized it, which is where I had the conversation with drjd
  • state cluster (or class)+functions which operate on it (the 'model' for lack of a better term)
    • state cluster for any ui-specific data
    • local, "redraw" user event which uses one or both state clusters to (in most cases) refresh the UI from scratch

So my "decoupling" occurs in only one place -- my state cluster and the functions which act on it are separate from anything specific to a given user interface. 

Edited by smithd
Link to comment
On August 25, 2017 at 10:24 AM, candidus said:

Currently I see the following options:

1) User events from/to the state machine. This seems to be a quite clean
approach but means a lot of work.

Note that there are two schools-of-thought on how to use User Events.   One is to have a separate custom Event for every different message (about 40 in your case).  For examples of this see the DQMH or JKI’s “State Machine Objects”.  These rely on scripting tools to manage all the custom subVis that need to be generated.   The other school is to use a single User Event to carry all messages, using something generic like a string “message label” and a Variant “data” payload.  “Messenger Library”, if you look under the LVOOP hood, is basically this.  At its simplest, you can do this later method with only a single “Send Message” subVI (with “label” and “data” inputs) that you place everywhere you were previously setting a control.  

So this is not a lot of work, or at least, not more than any other possible method.

  • Like 1
Link to comment

I prefer having N user events, one for each message I want, and one for each reply (if applicable).  If the data types are all the same (ie. Variant) you can put these user events into an array, and register for all of them at once.  You will have one event case handling all of them, but the point is that you can have a publisher/subscriber model where modules (actors) can subscribe to N of them at once.

Link to comment

There is a reason to refactor: I have to add more features and I don't want my toplelvel VI to become any bigger - currently it has a huge block diagram...

 

On 26.8.2017 at 6:21 PM, drjdpowell said:

The other school is to use a single User Event to carry all messages, using something generic like a string “message label” and a Variant “data” payload.

So this is not a lot of work, or at least, not more than any other possible method.

That's it! I began to experiment with an event containing a variant-based lookup table, then I looked at Messenger Library and saw that all I need is already there.

Thanks!

Link to comment
On 8/26/2017 at 0:21 PM, drjdpowell said:

Note that there are two schools-of-thought on how to use User Events.

I tend to bound back and forth depending on the application.  At some point, it just becomes way too many User Events to try to keep track of when you use individual events.  I think I only had 1 application where this became an issue.  But on the other hand, it is quite nice to only register for the events/messages that that specific process cares about instead of having to process all messages.

Link to comment
1 hour ago, shoneill said:

+1 for multiple User Events (why do away with the strict typing of LV)

While I do multiple user events, the payload is all Variant, which is again how I can get away with building them into an array and registering for them all at once, or subscribing and unsubscriping to events at runtime..  The scripting code generates the VIs that flatten to, and unflatten from, the type def so everything is still strictly typed, but there is a common payload for all events.  If there were N events, all with their own registered case in an event structure I couldn't do this as easily.  But I admit other parts of the application would be cleaner.

Link to comment
23 minutes ago, hooovahh said:

While I do multiple user events, the payload is all Variant, which is again how I can get away with building them into an array and registering for them all at once, or subscribing and unsubscriping to events at runtime..  The scripting code generates the VIs that flatten to, and unflatten from, the type def so everything is still strictly typed, but there is a common payload for all events.  If there were N events, all with their own registered case in an event structure I couldn't do this as easily.  But I admit other parts of the application would be cleaner.

While I see the pacticality of this, it makes me feel all icky doing it.......

Link to comment
On 29.8.2017 at 4:04 PM, Neil Pate said:

I usually have one user event per "actor" which is used to broadcast when its state changes. Recently I have been playing around with not using the event data terminal and instead having a global data repository implemented using a DRV to a variant (with the data stored as named attributes), the actors user event data is a timestamp that I set when I generate the event.

If I had a cent for every time I have created and deleted the register event terminal which never propagates properly...

Yes, the really crappy propagation of registration refnums is perhaps the major obstacle to the 1:1 command:event organisation.  That and the Event Structure mixing up the order of events whenever I add another event to the API......

  • Like 1
Link to comment

Although strict-typing is a valid reason to argue for separate User Events for each “message”, the freedom to Register selectively for messages is something that can be achieved in other ways.   The “ObserverRegister” in “Messenger Library” allows selective registration.  Also the flip side of the advantages of strict-typing are the advantages of being able to write generic code.

  • Like 1
Link to comment
  • 3 weeks later...

While we're on the user event bug train, I have seen this bug also manifest itself in the form of user events where I start getting bizarre coercion dots in my event cases and types not updating/propagating until I do a save all. It has resulted in "phantom time" being added to execution of subVIs where I benchmark around a VI that takes say, 400 ms to execute, but if I benchmark around all the code inside the VI it takes 50ms. I lose 350 ms in the ether.   

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.