Results 1 to 4 of 4

Thread: [LAPE] VariantInvoke() and exceeding it's limits

  1. #1
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default [LAPE] VariantInvoke() and exceeding it's limits

    Introduction:

    VariantInvoke is powerfull tool inbuilt in Simba, which lets you to pass, as an argument, one function to another function and call it repeatedly inside it's body. It's LAPE equivalent of PascalScript's CallFunc().

    Syntax:

    Simba Code:
    function VariantInvoke('functionToInvoke': string; parametersOfFunction :TVariantArray) : Variant;

    Variant is very neat thing - it's basically a type, which can contain value of any basic type , such as: integer, byte ,string ,extended. Also, you can assign any basic type to Variant directly, without any casting (so be carefull here):
    Simba Code:
    var i:integer;
    var v : variant;
    var s :string;
    begin
     v := 50;
     i := v; // no problem
     s := v; // compiler won't complain but you will get here error or access violation
    end;
    So array of Variant ( = TVariantArray) can contain many values of different type, such as:
    Simba Code:
    var va : TVariantArray;
    i : integer;
    begin
      va := ['Hello', 0.35 , 76543, i ];
    end;

    In VariantInvoke we use TVariantArray to store all arguments of function we call. And the result of VariantInvoke() is stored in Variant. Let's see an example, how it works:

    Simba Code:
    function MyNumber ( s :string; a,b :integer ) : integer;
    begin
       writeln(s +' args: ' + tostr(a)+ '  '+ tostr(b));
       result := a*b;
    end;

    procedure VariantTest(f:string;va:TVariantArray;howmany : integer);
    var i,r : integer;
    begin
      for i := 0 to howmany-1 do
      begin
         va[1] := va[1] * 2;
         r := VariantInvoke(f,va);
         writeln('Result : '+tostr(r));
      end;
    end;

    begin
      VariantTest('MyNumber',['Hello',5,7],6);
    end.

    It will print:
    Code:
    Hello args: 10  7
    Result : 70
    Hello args: 20  7
    Result : 140
    Hello args: 40  7
    Result : 280
    Hello args: 80  7
    Result : 560
    Hello args: 160  7
    Result : 1120
    Hello args: 320  7
    Result : 2240
    Limits:

    As I've said already Variant can store only basic types. That makes it useless with functions dealing with TPoints ,TPointArrays, TIntegerArrays etc. ( all arrays, records , custom types at all), which are quite frequent in SRL. Furthermore, due to way how VariantInvoke works ( it runs through code before compiler, I won't go further on this topic) it can't invoke functions, which are methods of types ,such as:
    Simba Code:
    customType.anyFunc();
    tabBackpack.isFull();
    TRSGameTabs.isTabVisible();
    TSomething.anything.objectX.getMyValue();
    which is a big pain, because SRL-6 and many other libraries are object-oriented.

    Reassuming, there are 3 limits of VariantInvoke():

    • Functions, which return non-basic type
    • Functions, which have non-basic types as parameters
    • Type.function()


    In the next part I will show and explain how to go around each of those, so you can use it freely with SRL and other libs.
    Last edited by bg5; 03-26-2015 at 01:00 AM.

  2. #2
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default

    VariantInvoke() with functions, which return non-basic type

    Let's consider a function abc() : TPoint ,which we want to pass to function test() :

    Simba Code:
    function abc() : TPoint;
    begin
      Result := Point(300,200);
    end;


    procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
    var i : integer;
    p : TPoint;
    begin
      for i := 0 to howmany-1 do
      begin
        p := VariantInvoke(FuncToInvoke,args);
        writeln(p);
      end;
    end;

    begin
      test('abc',[],10);
    end.

    Of course compiler will say something predictable:
    Code:
    Error: Can't assign "Variant" to "record [0]Int32; [4]Int32; end" at line 14
    Indeed variant can't store TPoint, but it can store..integer containing adress of our TPoint. So here is a trick !

    1. STEP 1
      We need to make a wrapper of abc() returning address of result instead of result itself :
      Simba Code:
      function abc_Wrap(addr_result : integer) : integer;
      begin
         TPoint(Pointer(a_result)^) := abc();
      { this syntax can be confusing to you so I can explain a bit: first we cast our address to pointer.
      Pointer is basically nothing else then 'cell' in memory containing address of another 'cell' in memory,
       in this case our variable of TPoint type. So we grab the variable to which Pointer is pointing and
       change it's value to result of original function. }

         Result := addr_result;
      end;
    2. STEP 2
      Oh wait, we are dealing with an address of something, but where is that thing? Indeed we need some place in memory, where the result will be stored. So we declare variable in global scope and add it above our wrapper:

      Simba Code:
      var abc_Wrap_Result : TPoint;
      function abc_Wrap(addr_result : integer) : integer;
      begin
         TPoint(Pointer(a_result)^) := abc();
         Result := addr_result;
      end;

    3. STEP 3

      We need to change test() function to handle wrapper:

      Simba Code:
      procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
      var i, ap : integer;
      p : TPoint;
      begin
        for i := 0 to howmany-1 do
        begin
          ap := VariantInvoke(FuncToInvoke,args); // = @p (address of our TPoint) ; I would prefer to do it in one line, but compiler would cry for some reason
          p := TPoint(Pointer(ap)^);
          writeln(p);
        end;
      end;
    4. STEP 4

      Everything altogether:

      Simba Code:
      function abc() : TPoint;
      begin
        Result := Point(300,200);
      end;

      var abc_Wrap_Result : TPoint;
      function abc_Wrap(a_result : integer) : integer;
      begin
         TPoint(Pointer(a_result)^) := abc();
         Result := a_result;
      end;

      procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
      var i,ap : integer;
      p : TPoint;
      begin
        for i := 0 to howmany-1 do
        begin
          ap := VariantInvoke(FuncToInvoke,args);
          p := TPoint(Pointer(ap)^);
          writeln(p);
        end;
      end;

      begin
        test('abc_Wrap',[integer(@abc_Wrap_Result)],10);
      end.




    VariantInvoke() with methods

    Look:

    Simba Code:
    type TMyType = record
     P : TPoint;
    end;

    function TMyType.getX(really : boolean) : integer;
    begin
      if really then
        Result := self.P.x;
    end;

     procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
    var i,ap : integer;

    begin
      for i := 0 to howmany-1 do
      begin
        ap := VariantInvoke(FuncToInvoke,args);
        writeln(ap);
      end;
    end;

    var
      mt : TMyType;
    begin
      mt.P := Point(123,999);
      test('mt.getX',[TRUE],10);
    end.

    Compiler won't notice anything wrong, just as like as you put any random function name, but it won't work, so we need to fix it. As you propably guess it will be done in very similar way as above, except we don't need to declare any variable, because our object already exists and the name of my wrapper will be more ugly Also we don't modify test() function, because this function returns basic type. So there is only one step ! :

    Simba Code:
    program new;

    type TMyType = record
     P : TPoint;
    end;

    function TMyType.getX(really : boolean) : integer;
    begin
      if really then
        Result := self.P.x;
    end;

    procedure test (FuncToInvoke : string; args :TVariantArray; howmany : integer);
    var i,ap : integer;
    begin
      for i := 0 to howmany-1 do
      begin
        ap := VariantInvoke(FuncToInvoke,args);
        writeln(ap);
      end;
    end;

    // nothing have changed till here
    function Wrap_TMyType_getP(TMyTypeAdress: integer; really : boolean) : integer;
    begin
      Result := TMyType(Pointer(TMyTypeAdress)^).getX(really);
    end;
    var
      mt : TMyType;
    begin
      mt.P := Point(123,999);
      test('Wrap_TMyType_getP',[integer(@mt),TRUE],10);
    end.



    VariantInvoke() with functions,which have non-basic types as parameters

    later...
    Last edited by bg5; 10-15-2017 at 02:51 AM.

  3. #3
    Join Date
    Oct 2011
    Posts
    805
    Mentioned
    21 Post(s)
    Quoted
    152 Post(s)

    Default

    Tracking Mouse
    Simba Code:
    {*******************************************************************************
    Procedure windTrackingMouse();
    By: Flight & bg5
    Description (Flight): Mouse movement based on distance to determine speed. Default slowed
                 speed at 20% from destination, if Double is set to true then slowed
                 speed also starts at origin and ends 80% to destination.

    Description (bg5): Variation of Flight's brakeWindMouse from aerolib. Calls function
    (returning address to TPoint) every refreshingDelay seconds to update destination.
    That function must be wrapped properly!
    All changes are marked with //*
    *******************************************************************************}

    Procedure windTrackingMouse(xs, ys, gravity, wind, minWait, maxWait,
              targetArea: extended; double: boolean;
              funcRetTPointAdr:string; args:TVariantArray; refreshingDelay :integer ); //*
    var
      T,rD : Timer;   //*
      xe, ye , ap :integer; //*
      Pe :TPoint;    //*
      veloX,veloY,windX,windY,veloMag,dist,randomDist,lastDist,D: extended;
      lastX,lastY,MSP,W,TDist: integer;
      sqrt2,sqrt3,sqrt5,PDist,maxStep,dModA,dModB,nModA,nModB: extended;
    begin
      ap := VariantInvoke(funcRetTPointAdr,args);  // *
      Pe := TPoint(Pointer(ap)^); // *
      xe := Pe.X; //*
      ye := Pe.y; //*
      rD.start(); //*

      MSP  := MouseSpeed;
      sqrt2:= sqrt(2);
      sqrt3:= sqrt(3);
      sqrt5:= sqrt(5);

      TDist := Distance(Round(xs), Round(ys), Round(xe), Round(ye));
      if (TDist < 1) then
        TDist := 1;

      dModA := 0.88;
      dModB := 0.95;

      if (TDist > 220) then
      begin
        nModA := 0.08;
        nModB := 0.04;
      end else if (TDist <= 220) then
      begin
        nModA := 0.20;
        nModB := 0.10;
      end;



      T.start();
      repeat
        if (T.timeElapsed() > 10000) then
          break;
        if (rd.timeElapsed() > refreshingDelay) then //*
        begin
            ap := VariantInvoke(funcRetTPointAdr,args);  // *
            Pe := TPoint(Pointer(ap)^); // *
            xe := Pe.X; //*
            ye := Pe.y; //*
            rD.start(); //*
        end;


        dist:= hypot(xs - xe, ys - ye);
        wind:= minE(wind, dist);
        if (dist < 1) then
          dist := 1;
        PDist := (dist/TDist);
        if (PDist < 0.01) then
          PDist := 0.01;

        if Double then
        begin
          if (PDist <= dModA) then
          begin
            D := (Round((Round(dist)*0.3))/5);
            if (D < 20) then
              D := 20;

          end else if (PDist > dModA) then
          begin
            if (PDist < dModB) then
              D := RandomRange(5, 8)
            else if (PDist >= dModB) then
              D := RandomRange(3, 4);
          end;
        end;

        if (PDist >= nModA) then
        begin
           D := (Round((Round(dist)*0.3))/5);
          if (D < 20) then
            D := 20;
        end else if (PDist < nModA) then
        begin
          if (PDist >= nModB) then
            D := RandomRange(5, 8)
          else if (PDist < nModB) then
            D := RandomRange(3, 4);
        end;

        if (D <= Round(dist)) then
          maxStep := D
        else
          maxStep := Round(dist);

        if dist >= targetArea then
        begin
          windX:= windX / sqrt3 + (random(round(wind) * 2 + 1) - wind) / sqrt5;
          windY:= windY / sqrt3 + (random(round(wind) * 2 + 1) - wind) / sqrt5;
        end else
        begin
          windX:= windX / sqrt2;
          windY:= windY / sqrt2;
        end;

        veloX:= veloX + windX;
        veloY:= veloY + windY;
        veloX:= veloX + gravity * (xe - xs) / dist;
        veloY:= veloY + gravity * (ye - ys) / dist;

        if (hypot(veloX, veloY) > maxStep) then
        begin
          randomDist:= maxStep / 2.0 + random(round(maxStep) div 2);
          veloMag:= sqrt(veloX * veloX + veloY * veloY);
          veloX:= (veloX / veloMag) * randomDist;
          veloY:= (veloY / veloMag) * randomDist;
        end;

        lastX:= Round(xs);
        lastY:= Round(ys);
        xs:= xs + veloX;
        ys:= ys + veloY;

        if (lastX <> Round(xs)) or (lastY <> Round(ys)) then
          moveMouse(Round(xs), Round(ys));

        W := (Random((Round(100/MSP)))*6);
        if (W < 5) then
          W := 5;
        if Double then
          if (PDist > dModA) then
            W := Round(W*2.5)
        else
          W := Round(W*1.2);
        wait(W);
        lastdist:= dist;
      until(hypot(xs - xe, ys - ye) < 1)

      if (Round(xe) <> Round(xs)) or (Round(ye) <> Round(ys)) then
        MoveMouse(Round(xe), Round(ye));

      MouseSpeed := MSP;
    end;

    {*******************************************************************************
    By: Flight & bg5
    Description(Flight): Makes use of BrakeWindMouse; mouse speed decreases at 15% to
                 destination point.  If Double is set to true then speed starts
                 slow and increases through the first 15% of the path.
    Description (bg5): Variation of Flight's brakeMMouse from aerolib. Calls function
    (returning address to TPoint) every refreshingDelay seconds to update destination.
    That function must be wrapped properly!
    All changes are marked with //*
    *******************************************************************************}

    Procedure TrackMMouse(double: Boolean;             //* randomization of destination point not needed
                  funcRetTPointAdr:string; args:TVariantArray; refreshingDelay :integer); //*
    var
      randSpeed : extended;
      X,Y,MS    : integer;
    begin
      MS        := MouseSpeed;
      randSpeed := (random(MouseSpeed) / 2.0 + MouseSpeed) / 10.0;
      getMousePos(X, Y);

      windTrackingMouse(X, Y,                                             //*
        8, 3, 10.0/randSpeed, 15.0/randSpeed, 10.0*randSpeed, double,
        funcRetTPointAdr,args,refreshingDelay);                         //*
      MouseSpeed := MS;
    end;

    Example of use: (remember you can use ANY detecting function which returns TPoint with Tracking Mouse);

    TReflectGroundItem.GetMSPoint() wrapped for Tracking Mouse
    Simba Code:
    var res_Wrap_TReflectGroundItem_GetMSPoint : TPoint;
    function Wrap_TReflectGroundItem_GetMSPoint(adr_self, adr_result : integer) : integer;
    begin
       TPoint(Pointer(adr_result)^) := TReflectGroundItem(Pointer(adr_self)^).GetMSPoint();
       Result := adr_result;
    end;

    Putting them together:
    Simba Code:
    function TReflectGroundItem.Grab2() :boolean;
    begin
    {...}

      TrackMMouse(TRUE,'Wrap_TReflectGroundItem_GetMSPoint',
                  [integer(@self),integer(@res_Wrap_TReflectGroundItem_GetMSPoint)], 100);
             Reflect.Mouse.Click(Mouse_Right);
             if Reflect.Text.ChooseOption(self.GetName,50) then
             begin
              Result := TRUE;
              break;
             end;    
    {..}        

    end;

    Effect:

    with MouseSpeed := 10;



    Credits to:
    @Olly - for little help with code.
    @Flight , @elfyyy - for function
    Last edited by bg5; 03-26-2015 at 02:52 AM.

  4. #4
    Join Date
    Mar 2015
    Posts
    438
    Mentioned
    21 Post(s)
    Quoted
    211 Post(s)

    Default

    Sweet guide was looking for something like this after my talk with @Obscurity;

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •