I have spent some time this weekend trying to figure out how to build the API for the tag database access functions in the library. The existing functions fall apart a little bit when we start talking about custom data types. I think that I have come up with a nice interface and I wanted to throw it out into the wild and get comments.
All of the tags in the database are stored as bitmaps, pointed to by a 'void *data' pointer in the tag definition structure. If the tag is a single BOOL then the *data pointer points to a single byte. If it's an array of 16 BOOLs then there are two bytes. If the tag is an array of DINTs then the bitmap is 4 bytes multiplied by the number of DINTs in the array. This is also how the tags with custom datatypes are stored. If we have a CDT that defines three BOOLs and then one DINT the bitmap for a single tag of this type would be 5 bytes long. One byte for the three BOOLs and 4 for the DINT. If we have an array of 3 of these types the bitmap will be allocated as 15 bytes. 5 for each item in the array.
At first I really wanted to keep the module developer from having to worry about any of the implementation details of how this data was stored, but that seemed to make the API very complex and required passing a lot of strings around inside the library as well as back and forth across the network to the server. This seemed wasteful. I think that I have settled on allowing a couple of different methods for accessing the tag database from the module library. One would be very simple but possibly not very efficient and the other would require the module developer to deal with how the data is stored in the database.
Any given piece of data in the tag database can be identified by four pieces of information. The first is the tag index. This is the unique ID of the tag in the server. Right now it is the index into the array where the tag information is kept. The second is a byte offset into the *data buffer where the actual real time data is stored and a bit offset if we are talking about BOOLs. Also a byte count to determine the size. This uniquely identifies any tag or portion of a tag. Any location like this can be represented as a text string as well. For instance...
Given a CDT named TestCDT that is defined as follows...
TestBit1 BOOL
TestBit2 BOOL
TestBit3 BOOL
TestInteger INT
TestArray DINT[10]
Now let's assume we create an array of 10 of these called TestTag. TestTag will use 430 bytes of memory. The size of the datatype is 43 bytes. One for the three bits, 2 for the Integer and 40 for the 10 Double Integers. If we want to refer to the whole tag we use the string "TestTag." The four pieces of information might be 3 for the tag index, the byte offset will be zero the bit offset will be zero and the size will be 430. If we want the TestInteger from the first item then we'll use a string like "TestTag[0].TestInteger" This would give us 3 for the index (that won't change) 1 for the byte offset , zero for the bit offset and 2 for the byte count. We could access further into bitmap with strings like "TestTag[4].TestArray[3]" I don't want to figure out what that offset is but you can see what I am talking about. Each of these locations can be accessed with these four pieces of information. I am going to call these four pieces of information the 'handle.' I should probably add the 'type' to the information. So for the last example the datatype that would be part of the handle will be DINT.
I should be able to create a set of functions that retrieve and manipulate these handles. This may seem a bit complex at first but for modules that will be using the same datatypes over and over again this would be the most efficient way to deal with them. The fact is that most modules are going to be dealing with the same tags the whole time that they are running and to constantly be having to convert form the string to the handle is going to use a lot of clock cycles for the same task over and over again. The module can store these handles for the data that it wants to manipulate and then write that data to the server as it sees fit. The strings for this data could be discarded after the module is configured.
I should also give the module developer some simple routines for reading and writing tags that does all this string conversion stuff each time. This makes it easier for module developers to get started and it also makes sense for modules that will not be using the same data over and over again. The most obvious example of this is the command line client module. This module will either be started by a user and used to view or manipulate a few tags, or it will be started by shell scripts and it may never use the same tag twice in it's lifetime. In this case it would be much easier to simply call a function like... dax_write_data("TestTag[3].Integer", buff, 1); This makes some assumptions about the datatype that I may not be comfortable with. Perhaps just forcing the module developer to deal with the details might be better.
This is still a work in progress and writing it down helps me think about it. It's a typical computer science compromise. I could make it very simple for the module developer by placing a lot of recursive and memory intensive code in the module or generating a lot of traffic on the server or I could make it easy on myself and pass all the details on the to the module developer. Somewhere in the middle is the right answer and I need to find a nice clean API. As always comments are welcome.