Showing posts with label component. Show all posts
Showing posts with label component. Show all posts

Friday, 17 June 2016

Putlistitems - successive occurrences

I've used putlistitems a lot, it's very useful.  Different switches all it to do different things, so for example, /occ will populate a variable with an associative list of field names and values, something like this...

  putlistitems/occ varMyList,"ENT"

"varMyList" will now be something like "FIELD1=VALUE1·;FIELD2=VALUE2·;etc".

This can further be modified by adding /modonly, which will only add field values to the list which have been modified since the record was retrieved, something like this...

  putlistitems/occ/modonly varMyList,"ENT"

However, the one I tend to use most is /id.  This will take the values of the specified field from multiple occurrences and put them into a list, something like this...

  varMyList = "FIELD1"
  putlistitems/id varMyList

"varMyList" will now be something like "VALUE1·;VALUE1·;etc", repeated for each occurrence.

This allows you to quickly grab the values of the field(s) you're interested in, without having to loop through the occurrences in order to build the list, which is much better for performance.

Or you can also use it to update the representations in a list, for example, backing up a load of field or register values...

  varMyList = "$1·;$2·;$3"
  putlistitems/id varMyList

"varMyList" will now be something like "$1=REG1·;$2=REG2·;$3=REG3".

All of this is well documented in the Uniface manuals, so you may well be asking me why I'm telling you this.  Fair question.

I never realised that the first use of /id, taking the values of the specified field from multiple occurrences, was actually successive occurrence.  This means that it will start on the current occurrence and loop through to the end, completing the hitlist if required.

The manuals make this clear, with the following note...
To ensure that the entire set of occurrences in the component is being addressed, make the first occurrence the current occurrence.

What this means is, use setocc first, like this..

  setocc "ENT",1
  varMyList = "FIELD1"
  putlistitems/id varMyList

I hope I've not been caught out by this in the past!

Summary: putlistitems/id will look at successive occurrences, so if you want the whole hitlist, remember to make sure the current occurrence is the first occurrence.

Friday, 24 July 2015

Where to put your code

There's an interesting blog post by Dennis Vorst gone up on the Uniface.info blog, titled Where to put your code.  As the title suggests, it's about which places are best for different types of code, based on their content.  

There are a number of options; conceptual at the entity or field level, locally at the entity or field level, within the component, a local procedure or a global procedure.  All of these have their merits.  

I've posted my response, defending the mighty global procedure, but why don't you have a read and add your opinion in the comments?

Summary: Check out this blog post... http://unifaceinfo.com/where-to-put-your-code

Monday, 30 March 2015

Indirection with dollar registers

Indirection in Uniface is a wonderful thing.  You can populate a local/component/global variable with a field name, and then use indirection to get the field value of the field that the variable references.  You can also use it to set the referenced field to a value, like this...

  fld1 = "fld2"
  @fld1 = "Test"
  if ( fld2 = "Test" )
    ;this will be true
  endif

In this example, using the "@" character to indicate indirection, it's the field which is referenced by "fld1" and not "fld1" itself which is updated.

You can even do recursive indirection, which is very cool!  Not something I've had a need for myself, but very clever all the same.

However, today I was trying to populate a register the same way, using code something like this...

  reg50 = "$50"
  @reg50 = "Test"
  if ( $50 = "Test" )
    ;this will be false!
  endif

In this example, the register was still blank (or whatever the register was before hitting this code).

To be fair, this is clearly stated in the Uniface manuals...

Only fields can be referenced indirectly; the name reached by the indirect reference cannot be a variable. The following example is incorrect because the indirection refers to a general variable rather than to a field:

$1 = "$99"

@$1 = "This string does not go to $99."


But I still wanted to be able to set registers in an indirect way, so I came up with this alternative...

  putitem/id list,"$50","Test"
  getlistitems/id list
  if ( $50 = "Test" )
    ;this will be true
  endif

This works by populating a list with the register and the value, and then uses getlistitems to populate them.  The "/id" mode copies items from the associative list into one or more fields, or registers in this case.

For example, you could use a simple for loop to clear out all of the dollar registers...

  for count = 1 to 99
    putitem/id list,"$%%count%%%",""
  endfor
  getlistitems/id list

This would set each of the 99 dollar registers to blank.

Summary: Indirection works great for fields, but not for registers - however, you can use getlistitems to achieve the same outcome.

Tuesday, 17 June 2014

What are handles and should I be using them?

The Uniface manuals describes a handle as...
A handle is a reference to an object, such as a component instance, entity, occurrence, or field. Handles can be used to call operations on the referenced object, or to pass these objects as variables and parameters.

If you're used to pointers, you may at first glance think that these are the same, but they're not.  A pointer contains the address of an item in memory and is therefore fixed, whereas a handle is an abstract reference, which means the address can be changed without breaking the reference.  This makes them very useful.

In Uniface you can have "public" or "partner" handles, which allow you to define which types of operations can access them.  They are also datatypes available for local, component and global variables, depending on the scope required.  I'm not going to discuss either of these points in detail, but I think it's handy to know.

As the description in the manual states, you can create a handle that references most Uniface objects, and there are different methods for creating these handles:  Component instances ($instancehandle), entity collections ($colhandle), entity occurrences ($occhandle), fields ($fieldhandle) and also OCX controls ($ocxhandle).  

I'm going to focus on component instances.  More specifically, I'm going to focus on creating new component instances, using newinstance and handles.  

As you might expect, creating a new instance of a form has a little lead time.  Using a handle doesn't stop this lead time, but what it does do, is mean that it only has to happen once.  This means that using a handle for a single form activation is not going to give you any advantage (unless you just love handles!) but when calling operations on the same form multiple times, handles could give you a performance improvement.

This is something like how you could activate a form...

  activate "TEST_FORM".test_oper("Test")


In fact this does an implicit newinstance as well (if it can't already find an instance with the same name as the form) and so explicitly your code could look like this...


  newinstance "TEST_FORM","TEST_FORM"
  activate "TEST_FORM".test_oper("Test")


If you were to replace this with a handle then you may use something like this...


  newinstance "TEST_FORM",theHandle
  theHandle->test_oper("Test")


Notice that in this case we have used a handle instead of an instance name in the newinstance call, and then we can use this handle in place of the activate command.  The -> is referred to by Uniface as the "dereference" operator, and is processed with a precedence of 2 (with only field indirection being higher).

So let's try these two different ways of activating an operation (in this case a very simple operation that simply returns out immediately).  The newinstance command is done before the loop, and the activate (or equivalent handle call) is done inside the loop, 200,000 times...


  • Using activate: 2.56, 2.61, 2.60 (about 2.6 seconds)
  • Using a handle: 2.39, 2.35, 2.39 (about 2.4 seconds)


As you can see, using a handle is marginally quicker, although you'd have to be making a lot of calls before you really noticed the difference.  

I thought that this would be even more useful when the handle references a component instance that is running on another server, such as when a form is mapped to run elsewhere, as the I thought the initial creation of the new instance would take longer here.  So I ran the same operation 20,000 times (after mapping the form)...


  • Using activate: 13.39, 14.35, 12.99 (about 13.5 seconds)
  • Using a handle: 13.56, 13.12, 12.91 (about 13.0 seconds)

Not much difference here either.  Having said that, maybe on a chatty network the difference would be greater.

Please note:  You must return out of the operation that you call, if you exit out then this will destroy the handle and future calls to the operation will fail with a $status of -1.  To clean up a handle you can either set it to 0 or "" (empty string). 


Summary: Handles are abstract references to pretty much any Uniface object.  They can be useful, especially if you want to call the same object multiple times, but they don't seem to have the performance benefits that I was expecting, not when it comes to component instances anyway.

Wednesday, 25 July 2012

User-defined functions

One of the things which I initially found a little odd, although very simple to grasp, was the way that Uniface allows you to create modules of code as an entry, not a function.  This entry has any number of parameters (well, there probably is a limit) which can be defined as "in", "out", or "inout".  The only thing that is returned from this entry is a numeric status, which you return and this sets $status accordingly.  I think it's fairly common that developers use a status of "0" to indicate successful and positive for additional successful status, then a negative status for errors.  Any other type of value could be returned in an "out" or "inout" parameter.


This is a very simple mechanism and it works nicely.  However, sometimes it can lead to some rather verbose code, as you have to put the call to the entry on a separate line, you can't nest the call within an if or while statement as part of the condition, for example.  Here's a simple entry which converts kilometres into miles...


  entry kms_to_mls_entr
  params
    numeric kms : in
    numeric mls : out
  endparams
    mls = kms*5/8
    return 0
  end


To convert two values using the entry and then add them, it takes 3 lines, like this...

  call kms_to_mls_entr(kms1,mls1)
  call kms_to_mls_entr(kms2,mls2)
  total = mls1 + mls2



From version 9 onwards (sorry, I can't remember the minor version number) you have another option.  It took me a while to find any information in the manuals, but it's there; they're called "User-Defined Functions".  Here's an example...

  entry kms_to_mls_func
  returns numeric
  params
    numeric kms : in
  endparams
    return kms*5/8
  end



Notice that there is a returns statement before the params, which determines what datatype should be returned.  In this case it is numeric, but it doesn't need to be.  In this case, to convert the two values using this function, it takes a single line...

  total = kms_to_mls_func(kms1) + kms_to_mls_func(kms2)

This can make the code a bit neater.  But of course I'm obsessed with performance, so my next step is the test these two methods over 2,000,000 iterations...


  • entry = 00:17.74, 00:17.69, 00:17.60 (under 18 seconds)
  • function = 00:13.64, 00:13.64, 0:13.67 (under 14 seconds)

So as you can see, the code is more concise and it performs better.  It also relies on less variables defined, so it's a win-win all round really.  You can even do this with a global procedure.



A bit of a side note; I thought you always had to specify a datatype when defining a parameter, and that this was always a local variable.  I discovered recently that you can also use a component variable or a painted field name, in which case you don't specify the datatype.  For example...


  entry kms_to_mls_entr
  params
    numeric kms : in
    $miles$ : out
  endparams
    $miles$ = kms*5/8
    return 0
  end


Summary: It is possible to create a "user-defined function", which is an entry that returns a specific datatype, allowing you to create more concise code, that also performs better. 

Wednesday, 16 May 2012

Performance of string manipulation - part one

One thing that every developer fights with (or should!!) is performance.  It's ok writing an application that works well for one developer sat messing about on a development server that only he is using, but it is very different when it's thousands of users all hitting the same server, under realistic but heavy load.


One particularly costly task is string manipulation.  Building up a string can use a relatively large chunk of memory and take a surprising amount of processor power.  So I decided to perform some performance tests to see how this could be improved.


The first test I did was comparing the use of a local variable against a non-database field.  I did this test by taking the following string...


" ABCDEFGHIJKLMNOPQRSTUVWXYZ 01234567890 abcdefghijklmnopqrstuvwxyz 01234567890"


...and then using putitem/id with a counter to add this 20,000 times, with the following results...

  • Non-database field = 02:42.14, 02:44.64, 02:43.44 (almost 3 minutes).
  • Local variable = 00:00.22, 00:00.23, 00:00.22 (less than 1 second).
  • Component variable = 00:00.22, 00:00.22, 00:00.22 (less than 1 second).
  • Global variable = 00:00.22, 00:00.22, 00:00.23 (less than 1 second.


As you can see, when manipulating a string you should never use a field to hold the string.  Having said that, it doesn't seem to make any difference which type of variable you use.  I continued testing the different types of variables with 200,000 and 2,000,000 iterations, without seeing the times deviate from each other.

Summary: Use a variable (any kind of variable) but not a field.