Showing posts with label RunBaseBatch. Show all posts
Showing posts with label RunBaseBatch. Show all posts

Monday, June 12, 2023

Complete pack/unpack pack for RunBaseBatch adventure

Usage

We can use pack-unpack methods in RunBaseBatch classes and all its descendants to save and/or store the state of an object, and then later re-instantiate the same object.

We need it, for example, to use the user's input for any batch job: some parameters can be used when a batch job is executed on the server. 

Documentation

Microsoft doc article is here.

Quote: "A reinstantiated object is not the same object as the one that was saved. It is just an object of the same class whose members contain the same information." 

You need to teleport your objects without a fly or other bugs.

Case examples

Basics

Basic example of such a class is Tutorial_RunbaseBatch.

    TransDate       transDate;
    CustAccount     custAccount;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        transDate,
        custAccount
    #endmacro
    public container pack()
    {
        return [#CurrentVersion,#CurrentList];
    }

    public boolean unpack(container packedClass)
    {
        Version version = RunBase::getVersion(packedClass);
        ;
        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList] = packedClass;
                break;
            default:
                return false;
        }

        return true;
    }

Containers

Basically you can add a container directly to pack() or convert a container to a string and save the latter. Opposite conversion required for restoring. 

container                   siteIds;
str                         siteIdsStr;

#define.CurrentVersion(1)
#define.Version1(1)
#localmacro.CurrentList
  siteIdsStr
#endmacro

public boolean getFromDialog()
{
  ...
  // convert it to string for pack/unpack
  siteIdsStr  = con2Str(siteIds);
  
  return super();
}

public void dialogPostRun(DialogRunbase _dialog)
{
    FormRun formRun;

    super(dialog);

    formRun = _dialog.dialogForm().formRun();

    if (formRun)
    { 
        // if we restored from last values
        if(siteIdsStr)
        {
            //then we convert the string to container
            siteIds = str2con(siteIdsStr);
        }
    }
}

See the following article for the complete usage https://alexvoy.blogspot.com/2014/11/dialog-field-with-multiple-choice.html

Tables

Save an unique key value, say, RecId to find a target buffer after unpack()

Collection types

Maps, lists, and sets are equipped with method pack, which you can use directly for packing.

        // collections
        List        list;
        Map         map;
        Set         set;
        // saved states
        container   packedCont;

        packedCont = list.pack();
        list = List::create(packedCont);

        packedCont = map.pack();
        map = Map::create(packedCont);

        packedCont = set.pack();
        set = Set::create(packedCont);

Queries

How to pack/unpack a query among other parameters, you can find in IntrastatTransfer class.

    QueryRun        queryRunIntrastatTransfer;

    container pack()
    {
        return [#CurrentVersion,#CurrentList,queryRunIntrastatTransfer.pack()];
    }

    boolean unpack(container packedClass)
    {
        Integer      version      = conPeek(packedClass,1);
        container    packedQuery;

        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList,packedQuery]      = packedClass;
                if (packedQuery)
                {
                    queryRunIntrastatTransfer = new QueryRun(packedQuery);
                }
                break;

            default:
                return false;
        }
        return true;
    }

Subclasses

How to pack/unpack additional parameters in a subclass.

class myVendCreateGlobalPaymJournal extends CustVendCreatePaymJournal_Vend

    NoYes       myParm;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myParm
    #endmacro
    /// <summary>
    /// Pack the new parameters
    /// </summary>
    /// <returns>standard list of parameters with the new ones</returns>
    public container pack()
    {
        return [#CurrentVersion,#CurrentList] + [super()];
    }

    /// <summary>
    /// Unpacks saved parameters
    /// </summary>
    /// <param name = "_packedClass">Parameters container</param>
    /// <returns>True if OK</returns>
    public boolean unpack(container  _packedClass)
    {
        Integer  version = conPeek(_packedClass,1);
        container packedBase;

        switch (version)
        {
            case #CurrentVersion:
                [version, #CurrentList, packedBase] = _packedClass;
                return super(packedBase);
        }
        
        return super(_packedClass);
    }

Extensions

How to pack/unpack additional parameters in an augmented class (extension).

/// <summary>
/// We are going to use a new additional parameter
/// </summary>
[ExtensionOf(classStr(<ClassName>))]
public final class My<ClassName>_Extension
{
    private boolean     myNewParm;   
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myNewParm
    #endmacro

    
    /// <summary>
    /// myNewParm access
    /// </summary>
    /// <param name = "_parm">boolean</param>
    /// <returns>boolean</returns>
    public boolean parmMyNewParm(boolean _parm = myNewParm)
    {
        myNewParm= _parm;
        return myNewParm;
    }

    /// <summary>
    /// Extends Pack
    /// </summary>
    /// <returns>container</returns>
    public container pack()
    {
        container packedClass = next pack();
        return SysPackExtensions::appendExtension(packedClass, classStr(My<ClassName>_Extension), this.myPack());
    }

    /// <summary>
    /// Extends Unpack
    /// </summary>
    /// <param name = "packedClass">container</param>
    /// <returns>boolean</returns>
    private boolean myUnpack(container packedClass)
    {
        Integer version = RunBase::getVersion(packedClass);
        switch (version)
        {
            case #CurrentVersion:
                [version, #currentList] = packedClass;
                break;
            default:
                return false;
        }
        return true;
    }

    /// <summary>
    /// Packs my locals
    /// </summary>
    /// <returns>container</returns>
    private container myPack()
    {
        return [#CurrentVersion, #CurrentList];
    }

    /// <summary>
    /// Extends unpack
    /// </summary>
    /// <param name = "_packedClass">container</param>
    /// <returns>boolean</returns>
    public boolean unpack(container _packedClass)
    {
        boolean result = next unpack(_packedClass);

        if (result)
        {
            container myState = SysPackExtensions::findExtension(_packedClass, classStr(My<ClassName>_Extension));
            //Also unpack the extension
            if (!this.myUnpack(myState))
            {
                result = false;
            }
        }

        return result;
    }

}

Originally from https://alexvoy.blogspot.com/2022/02/additional-parameters-in-runbasebatch.html

Please, ping me if I missed anything.

Friday, February 11, 2022

Additional parameters in RunBaseBatch class extension

 Old good pack/unpack patterns with SysPackExtension call. Say, we need to augment RunBaseBatch based class <ClassName>.


/// <summary>
/// We are going to use a new additional parameter
/// </summary>
[ExtensionOf(classStr(<ClassName>))]
public final class My<ClassName>_Extension
{
    private boolean     myNewParm;   
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        myNewParm
    #endmacro

    
    /// <summary>
    /// myNewParm access
    /// </summary>
    /// <param name = "_parm">boolean</param>
    /// <returns>boolean</returns>
    public boolean parmMyNewParm(boolean _parm = myNewParm)
{ myNewParm= _parm;
return myNewParm;
} /// <summary> /// Extends Pack /// </summary> /// <returns>container</returns> public container pack() { container packedClass = next pack(); return SysPackExtensions::appendExtension(packedClass, classStr(My<ClassName>_Extension), this.myPack());
} /// <summary> /// Extends Unpack /// </summary> /// <param name = "packedClass">container</param> /// <returns>boolean</returns> private boolean myUnpack(container packedClass) { Integer version = RunBase::getVersion(packedClass); switch (version) { case #CurrentVersion: [version, #currentList] = packedClass; break; default: return false; } return true; } /// <summary> /// Packs my locals /// </summary> /// <returns>container</returns> private container myPack() { return [#CurrentVersion, #CurrentList]; } /// <summary> /// Extends unpack /// </summary> /// <param name = "_packedClass">container</param> /// <returns>boolean</returns> public boolean unpack(container _packedClass) { boolean result = next unpack(_packedClass); if (result) { container myState = SysPackExtensions::findExtension(_packedClass, classStr(My<ClassName>_Extension));
//Also unpack the extension if (!this.myUnpack(myState)) { result = false; } } return result; } }

Saturday, March 12, 2016

Multi thread parallelism and a dispatching table for finding a minimum

In my free time I enjoy by solving programming puzzles from Advent of Code website. Some of them are pretty simple, though others could be tricky, however, all of them are always witty. Of course, I do it in AX so that I could use as much its power as possible.

The day 4 Ideal Stocking Stuffer became a die-hard to me. And it is not because of its "business complexity" -- you simply need to find the lowest positive number producing an MD5 hash for a given secret code, so that such a hash, in hexadecimal, starts with at least five zeroes.

Honestly, I have a vague idea about MD5 hash math -- I just took a working example and injected it into my class.

The stumbling point here was calculation time. Even for the first part of the puzzle, which is always easier than than the second one, it took so much time that I started flirting with the idea to improve performance.

Wrapping the MD5 hash calculation method so that it could be run in CIL got it faster but not enough to be happy.



The next idea was batch task execution in parallel threads, like it is brilliantly explained by Ganas1 in four chapter blog series:

Batch Bundling

However, we need to find the lowest positive number; therefore, we do not know how many tasks must be created. (Let's assume that we are limited with the maximum of Int64)


My solution is the following.

I created a table, which is to centrally dispatch creating, executing, and stopping batch tasks based on a separate, sequentially assigned positive number ranges. So, for each batch task it keeps the assigned thread number, ranges, execution status and found results, if any.



The batch task generating class creates them for a given number of logical processors, four in my environment.


Each task checks the table for a found result. If it is already found in any range, it stops.
If not, it looks for the highest range from the table and than tries to add a new record. In case of success, it runs finding the lowest number in the given range.

If such a number is found in the current thread, this value becomes a new candidate only if there are no results found in the lower ranges and no any lower ranges still running.


Now blood runs faster: even the second part of the job did not give me a pause to get another beer from the fridge.

However, it is up to your judgement to set up the right range size and number of parallel tasks. The smaller a single step is, the more the transaction cost will be. And vice-versa, the larger the range is, the longer you need to wait the higher ranges tasks to finish: the total execution time is the longest task's.


This project comprises examples of the following techniques:

  • dynamic dialog creation on RunBaseBatch
  • wrapping for execution in CIL
  • execution time calc
  • multiple batch task creation
  • try-catch exception handling for concurrent table updating
  • InteropPermission assertion


Happy AX mining!

Thursday, January 16, 2014

How to change Batch caption dialog field in run time. RunBaseBatch sample

This is an example of a RunBaseBatch class that demonstrates how to change another dialog fields in run time.

Let's say we have a parameter of enum type, which selects the right business logic inside of Run method.

It is a good idea to change the main static text of the dialog as well as the batch caption that serves as a description for an eventual batch job (and tasks).

After adding this field in Dialog method we need to override Modified method for this field.


protected Object dialog()
{
    FormComboBoxControl  combobox;
    dlg                 = super();

    dialogEventType = dlg.addFieldValue(EnumStr(uapInterfaceEventType), eventType );
    // to add details to the caption and task description in batch tasks
    dialogEventType.registerOverrideMethod(methodstr(FormStringControl, modified), methodstr(tmxRunBaseBatchSample, eventType_modified), this);
    // to avoid user input in the field
    combobox = dialogEventType.control();
    combobox.comboType(1);

    return dlg;
}

In eventType_modified method we call two additional methods to apply the user input respectively for BatchCaption and MainInstruction fields.

private boolean eventType_modified(FormStringControl _control)
{
    boolean ret = _control.modified();
    if(ret)
    {
        this.setBatchCaption();
        this.setMainInstruction();
    }
    return ret;
}

To get access to these dialog fields we use two different approaches. We find BatchCaption form control recursively inside of batch tab page based on its type.

private void setBatchCaption()
{
    FormStringControl                  batchCaptionControl;

    // to get the batch caption control; any of them if many
    batchCaptionControl = this.getBatchCaptionControl(dlg);

    if (batchCaptionControl)
    {
        batchCaptionControl.text(this.caption());
    }
}
// returns the batch caption form control if any in the dialog
private FormStringControl getBatchCaptionControl(DialogRunbase _dialog)
{
    FormStringControl                  batchCaptionControl;

    // recursive routine to look for the right form control of BatchCaption EDT
    Object findBatchCaptionControl(Object _parentObject)
    {
        int         i;
        Object      childControl;
        Object      foundControl;


        for (i = 1; i <= _parentObject.controlCount(); i++)
        {
            childControl = _parentObject.controlNum( i );

            // this is our boy
            if( childControl is FormStringControl && childControl.extendedDataType() ==  extendedTypeNum(BatchCaption))
            {
                // time to get up
                return childControl;
            }
            else
            {
                if (childControl is FormGroupControl)
                {
                    foundControl = findBatchCaptionControl(childControl);
                    if (foundControl)
                    {
                        return foundControl;
                    }
                }
            }
        }
        // just step back to check others
        return null;
    }
/////// main routine  /////////////////////////////////////////////////////////////
    if( _dialog && _dialog.batchDialogTabPage())
    {
        batchCaptionControl = findBatchCaptionControl(_dialog.batchDialogTabPage().control());
    }

    return batchCaptionControl;
}

As to MainInstruction we get it by its name.

private void setMainInstruction()
{
    FormStaticTextControl   mainInstructionControl;
    FormGroupControl        formGroup = dlg.mainFormGroup();

    // to get the main instuction static text of the dialog
    mainInstructionControl = dlg.dialogForm().runControl('MainInstruction');

    if (mainInstructionControl)
    {
        mainInstructionControl.text(this.caption());
    }
}

Now when the user changes the event type, two other fields change respectively.




You can find the whole project here.