Data types 2 with Bruce- QUEUE with ANY

Home work on looping the queue and deletes.

Tak for that as its something one might forget along with clearing ANY values in a queue.

I think from memory we loop from the bottom of the queue to the top by using BY -1

Records to 1 by -1

And this will make us check our libraries for this just in case there are examples of if lurking.

Clearing ANY in a queue is another one and we usually go over kill on this by loop through the queue and setting any ANY fields to NULL and doing a PUT. Then freeing the queue and disposing of it if its a typed ref queue.

The ABC Code in LibSrc is often a good example of Clarion code. Some of it was written by DAB or Gavin. ABUTIL Field Pairs manages a Queue of 2 ANY’s:

FieldPairsQueue QUEUE,TYPE
Left  ANY
Right ANY
  END

FieldPairsClass CLASS,TYPE,MODULE('ABUTIL.CLW'),LINK('ABUTIL.CLW',_ABCLinkMode_),DLL(_ABCDllMode_)
List              &FieldPairsQueue
AddPair           PROCEDURE(*? Left,*? Right),VIRTUAL
...
Kill              PROCEDURE
...
           END

As you describe you see KILL does set all the ANYs to NULL. It does not FREE the QUEUE because DISPOSE does that for you. Note PUT was not done.

FieldPairsClass.Kill PROCEDURE
I UNSIGNED,AUTO
  CODE
  IF ~SELF.List &= NULL
    LOOP I = 1 TO RECORDS(SELF.List)
      GET(SELF.List,I)
      SELF.List.Left &= NULL
      SELF.List.Right &= NULL
    END
    DISPOSE(SELF.List)
  END
  RETURN

The help on ANY is a MUST READ if you are going to use them. There is coverage of ANY and Queue. You have to remember an ANY can be used for an &= Reference Assignment, or a = Xxx is a NEW.

Once an ANY variable in a QUEUE has been reference assigned a variable (AnyVar &= SomeVariable), another reference assignment statement will assign a new variable to the ANY. This means the previous “pointer” is disposed of and replaced by the new “pointer.” If the first reference has already been added to the QUEUE, then that entry will “point at” a “pointer” that no longer exists.

Therefore, Queue fields of type ANY must be set to NULLs by executing the CLEAR statement with the QUEUE record structure itself as parameter, i.e. the program must execute CLEAR(Queue), before setting new values of queue field of type ANY for the next ADD() or PUT(). This is because CLEAR for QUEUEs and GROUPs are not applied recursively to the data pointed at from fields of reference types. Therefore, CLEAR(queue) just sets fields of type ANY to NULLs without disposing of their internal data.

In addition, you need to reference assign a NULL to all queue fields of type ANY (Queue.AnyField &= NULL), prior to deleting the QUEUE entry, in order to avoid memory leaks.

Below you see before an ADD the CLEAR(Q) is done to deal with the ANY’s

FieldPairsClass.AddPair PROCEDURE (*? Left, *? Right)
  CODE
? ASSERT(~(SELF.List &= NULL))
  CLEAR(SELF.List)
  SELF.List.Left &= Left
  SELF.List.Right &= Right
  ADD(SELF.List)
  RETURN

I think Jeff Slarve wrote a CMag article on QUEUE and ANY. I have trouble searching the new Jira archive.

We use ANY in queues all the time.

For reflecting database and queue fields in runtime controls.

For reflecting runtime databases using the russian DLIB code.

As Group break pointers in process classes ect.

It would be great for ANY to display in a LIST Control as so far we dont seem to gotten this working.

Same problem for &STRING in a LIST. You can do it with a VLB. Not too baf to code after you’ve done s few. Resort with a wrapper class.

Mark has his VLB Class

My BigBangTheory class uses a VLB to display the StringTheory Lines Q of &STRING so it’s an example.

ClarionLive had a series of webinars covering virtual list box

I don’t see the point of clearing the ANYs (or references) then doing a PUT
only to do a FREE(Q) later.

I normally wrap my queues in a class (see ctQueue)
CwUnit/ctQueue.inc at master · MarkGoldberg/CwUnit (github.com)

The .DESTRUCT calls .FREE,
.Free in turn loops calling .DEL()

  LOOP GET(SELF.BaseQ, 1)
	   IF ERRORCODE() THEN BREAK END
	   SELF.Del() 
  END

.DEL will call DELETE(SELF.BaseQ)

.DEL is a virtual method that is often derived,
in the derived method ANYs and REFERNCES are cleaned up
and then PARENT.DEL()

From a coding point of view, it’s nice in that I only need write code dealing with just one row.

That has always been my way of doing it too (getting the first record and disposing/deleting until they’re gone) but when I had millions of rows in the queue it seemed to be a significant bottleneck. So I loop backwards now like in the destructor here. https://github.com/jslarve/BitPairsClass/blob/master/JSBitPairsClass.clw

Here’s an example of the difference. In my VM, it took 64 seconds to delete 1 million rows using the GET(Q,1) method. But 0.18 seconds with GET(Q,Ndx).

image

 PROGRAM

StartTime     LONG
EndTime       LONG
Ellapsed1     REAL
Ellapsed2     REAL 

    MAP
      FILLQ
      KILLQ1
      KILLQ2
    END

Q  QUEUE
L    LONG
   END

  CODE

  FILLQ
  KILLQ1
  FILLQ
  KILLQ2
  MESSAGE('Ellapsed 1 - GET(Q,1): ' & Ellapsed1 & '|Ellapsed 2 - GET(Q,Ndx): ' & Ellapsed2 )

FILLQ PROCEDURE
Ndx LONG
  CODE
  FREE(Q)
  LOOP Ndx = 1 TO 1000000
    Q.L = Ndx
    ADD(Q)
  END
  
KILLQ1 PROCEDURE

  CODE
  
  StartTime = CLOCK()
  LOOP 
    GET(Q,1)
    IF ERRORCODE()
      BREAK
    END
    DELETE(Q)
  END
  EndTime = CLOCK() 
  Ellapsed1 = (EndTime - StartTime) * .01

KILLQ2 PROCEDURE
Ndx LONG

  CODE
  
  StartTime = CLOCK()
  LOOP Ndx = RECORDS(Q) TO 1 BY -1
    GET(Q,Ndx)
    IF ERRORCODE()
      BREAK
    END
    DELETE(Q)
  END
  EndTime = CLOCK() 
  Ellapsed2 = (EndTime - StartTime) * .01
1 Like

No point at all but to be certain we simply did a put just to be safe but after seeing the example this old habit may be forgotten!

Thank you.
I changed my ctQueue.Free() method based on your performance info

CwUnit/ctQueue.clw at master · MarkGoldberg/CwUnit (github.com)

2 Likes