LabVIEW Idea Exchange

cancel
Showing results for 
Search instead for 
Did you mean: 
0 Kudos
raphschru

Create/destroy a DVR with a runtime-defined data type

Status: New

Current state:

 

To create and destroy a DVR, its data type must be statically specified on the diagram:

raphschru_8-1770050580364.png

 

In case we want to abstract the data type for the creation and destruction of the DVR, we have to use workarounds like using a DVR of a variant:

raphschru_5-1770048875197.png

 

The problem is that it sacrifices data access efficiency for the sake of DVR creation/destruction genericity.

 

Idea:

 

Provide a way to create and destroy a DVR with a data type defined at runtime.

 

The "Create" node would take a variant for the its data type and output a U32 representing the reinterpreted strictly-typed reference.

The "Destroy" node would take the U32 and give back a variant containing the last value.

 

We could either have new nodes in the "Memory Control" palette, or just a new right-click option "Generic" on the existing nodes:

 

raphschru_7-1770049507065.png

 

It would allow to manage the creation and destruction of DVRs generically (reducing boilerplate code), while still having DVRs of strict types (improving efficiency compared to a DVR of a variant).

 

Example usage:

 

1. A malleable VI for batch creation of DVRs specified in a cluster of DVRs:

raphschru_9-1770052894033.png

 

 

2. Separation of data type definition and DVR instantiation:

raphschru_0-1770056394515.png

 

Regards,

Raphaël.

8 Comments
Intaris
Proven Zealot

"datatype defined at runtime"

 

How does that work within a strictly-typed language though?

 

There needs to be a clearly defined input and output variability, i.e. at which stage of the process exactly is the flexibility required. Presumably, the goal is to retain strictly-typed DVRs, otherwise there's practically nothing you can do with them....

 

So in essence, if you could simply define a malleable VI which would accept *anything" and outputs a DVR of that thing (specific to any given callsite), that is what you're asking for?

raphschru
Active Participant

Maybe "generic" or "variant-defined" would have been a more appropriate term than "runtime-defined". Strictly speaking, the data type of the DVR is defined at edit-time at one place of my code, where I can use a malleable VI. The problem is that I want to handle creation/destruction of that DVR at multiple other places of my code, without re-specifying the strict data type each time. So I have a generic class that stores the data type as a variant at definition, then I want to use that variant to instantiate the DVR when needed. In more concise terms, I want to separate the strict type specialization of the DVR from the instantiation of the DVR itself. Currently it is a single function to do both.

Intaris
Proven Zealot

So I have a generic class that stores the data type as a variant at definition, then I want to use that variant to instantiate the DVR when needed

 I don't understand. The call-site where the DVR is being instantiated will need a strict datatype.

 

You temporarily storing the information in a variant makes no real difference unless you want a DVR of type variant.

 

So what does "instantiate when needed" actually encompass? Sounds more like you're asking for generic classes than a generic DVR. Where does the specific type information for actually USING the DVR enter the equation?

raphschru
Active Participant

@Intaris wrote:

The call-site where the DVR is being instantiated will need a strict datatype.


Yes, this is where the current limitation is.

 


@Intaris wrote:

So what does "instantiate when needed" actually encompass? Sounds more like you're asking for generic classes than a generic DVR. Where does the specific type information for actually USING the DVR enter the equation?


In my RT application, I have a generic class called "API", which is used to communicate between asynchronous processes. In my context, an asynchronous process that receives, executes and replies to requests is called a "Server", and the different call-sites that send the requests and wait for reply from the server are called "Clients".

 

The server creates its own API at initialization. The API creation VI takes a variant as input, so that the server can "specialize" its own API, in particular the DVR types. The DVRs are one of the mechanisms used to efficiently pass complex data between the clients and the server.

 

Then, the API is published to the rest of the application and other processes that need to communicate with this server can "obtain a client" from that API. "Obtaining a client" in my context means:

 - instantiating a reply FIFO (for receiving relies to requests). The FIFO is generic, no need for strict typing.

 - instantiating the DVRs. Here the DVRs are specific to the particular server that created the API.

Each client needs its own instances of reply FIFO and DVRs. The "obtain a client" VI is a generic method of class "API", it is not aware of how the DVRs in the API have been specialized by a particular server. However, it contains a variant with the actual DVR types.

Intaris
Proven Zealot

I have done something similar, just not for DVRs. I've done it to provide modes of instantiating BRAM / Registers / FIFOS on FPGA and it has been very useful indeed.

 

Use Objects. Parent object is just "DVR" and the base class implements a dynamic dispatch "close" method. You could even include a "read Variant" method.

Now instantiate all versions of the classes (datatypes) you require. Work within your modules with knowledge of the strict datatype use the concrete class, all others can use the public methods of the parent.

 

Asking for a DVR without specifying the datatype is basically just asking for direct memory access. Not specifying the datatype means there is no information about what size the memory allocation should be, so there obviously needs to be some specific information somewhere. You cannot allocate memory without knowing what to allocate. I would suggest making that a conscious decision and visible via LVOOP. This is one of the more powerful uses of OOP, encapsulation. You can then encapsulate the strict data within the domain of the specific module functionality and everything outside sees the "generic" version.

raphschru
Active Participant

@Intaris wrote:

Use Objects. Parent object is just "DVR" and the base class implements a dynamic dispatch "close" method. You could even include a "read Variant" method.

Now instantiate all versions of the classes (datatypes) you require. Work within your modules with knowledge of the strict datatype use the concrete class, all others can use the public methods of the parent.


The problem is, for each async process (I have dozens of them), I already have a specific "Data" class that holds the inner state of the process, and I don't want to have a second specific class for the sole purpose of specializing the DVR creation in the public API. You're right this would be the most logical/elegant solution, but I would need a stronger reason to be forced to double the number of classes in my project.

 

My current solution is a poor man's alternative to dynamic dispatch:

 - Method "API:Create" takes a callback VI named "Manage DVRs" that will be in charge of creating/destroying the specific DVRs when needed;

 - Each server has to implement its own specific callback and pass it when creating its API.

 

In both ways, I still have to code a bunch of VIs with "Create/Destroy Data Value Reference" nodes (either manually or via scripting), while a dynamic create/destroy would have solved the problem in one shot.

 


@Intaris wrote:

Not specifying the datatype means there is no information about what size the memory allocation should be, so there obviously needs to be some specific information somewhere. You cannot allocate memory without knowing what to allocate.


The full data type info is available in the variant. Actually there isn't a more exhaustive and complete description of a data type than a variant (specifically the type descriptor part) in LabVIEW. Dynamic memory allocation allows to allocate memory with a size known at runtime.

Intaris
Proven Zealot

You can also create such classes via scripting.

 

Then if you still need a "manage" process, pass the concrete object to it and instantiate that (Dependency injection). Everything stays typed and it's no longer "poor-man's Dynamic dispatch" but just "dynamic Dispatch".

 

Given the presence of an actual alternative in LabVIEW, I think introducing this feature, for me, is not warranted given the possibility for memory access violations and all kinds of mayhem when LV inevitably messes up the conversions between generic and strict DVR types. The deeper you travel in the dungeon, the more careful and considered your steps need to be.

raphschru
Active Participant

@Intaris wrote:

You can also create such classes via scripting.


Indeed, but making a practical and robust scripting tool is a generally a huge amount of work (I've done this a lot in other contexts), and sometimes it creates more problems than it solves.

 

@Intaris wrote:

I think introducing this feature, for me, is not warranted given the possibility for memory access violations and all kinds of mayhem when LV inevitably messes up the conversions between generic and strict DVR types. The deeper you travel in the dungeon, the more careful and considered your steps need to be.


I've seen the technique of reinterpreting a strictly-typed refnum to e.g. a U32 used quite a lot in various applications. It is an easy way to abstract the strict data type of a refnum. I agree you need to know what you're doing though. For example, the GOOP toolkit stores the DVR as a U32 to allow object composition with itself.

 

Also, you can already make LV crash with an access violation by doing this:

 

raphschru_0-1770131941027.png