Shop OBEX P1 Docs P2 Docs Learn Events
Objects and returning multiple values — Parallax Forums

Objects and returning multiple values

Dr_AculaDr_Acula Posts: 5,484
edited 2012-02-26 12:21 in Propeller 1
Is there a 'best practice" way to return multiple values (longs) from an object?

I have started with a PUB in the main program. Pass it one value and (say) it modifies one value. That is easy
result := mypub(value)

Next is the scenario where you pass one value and it modifies two or more values.

Answer is to declare those variables as "global" variables in the VAR section.

Is that the best way though? Could you pass those variables as dummy values

result := mypub(value1, value2, value3)

and that pub has enough information to work out where, say, value2 is and hence can modify this?

Hmm - that won't work. Still needs to be a global VAR anyway.

The reason I'm asking is that I have some code in a "MAIN" routine and I want to port it over to an object. Now the values are no longer global.

What is the best way to do this?

Do you create a list of variables and pass the start of the list, and then the object can work out how to modify all those variables? What if you change the order of the list down the track? How much code space is wasted coding and decoding variables?

There are a number of solutions here. One is to use arrays and just pass the start of the array, and rather than calling variables names which mean things "xlocation" you refer to that as myarray[3]. This is possibly the most code efficient in terms of recreating local variables within an object, but it is harder to read.

Based on code I have seen in some objects I suspect the answer to this question is to declare lists of variables in the "main" program, pass the start address of the list, and put a comment at the beginning of the list "don't change the order of this list of variables!"

Is this the "best" way for an object to modify a number of global variables?

Comments

  • JonnyMacJonnyMac Posts: 9,197
    edited 2012-02-26 05:16
    Could you pass those variables as dummy values

    You can always pass pointers to your variables. If you want to pass three longs to a method, for example:
    object.method(@myvar1, @myvar2, @myvar3)
    

    The up-side is that you can use the same method with different variables and pass them in the order you choose. The rule, of course, is that you're passing pointers to known types (usually longs).
    Based on code I have seen in some objects I suspect the answer to this question is to declare lists of variables in the "main" program, pass the start address of the list, and put a comment at the beginning of the list "don't change the order of this list of variables!"

    This is what I tend to do, or if I have an object that uses an array (like a lighting controller) I have a method in the object that provides the hub address of that array so that external code can get to it directly.
    Is this the "best" way for an object to modify a number of global variables?

    "Best" is highly subjective when there are several valid ways to accomplish a task. As long as what you're doing is not confusing, and you provide sensible access methods, that should be enough.
  • localrogerlocalroger Posts: 3,452
    edited 2012-02-26 06:25
    If you keep multiple values in an array you can return the rval := @array. You can also do this if they are individually declared (you get at them with LONG[rval + index]) but you have to var them all together and not mix types.

    A good readable way to do this is to CON the indeces with an enum:

    CON

    #0
    first_parm
    second_parm
    third_parm

    ...and then refer to the results as LONG[rval + second_parm] etc.
  • Heater.Heater. Posts: 21,230
    edited 2012-02-26 10:07
    Then you have to decide if you want the block of variables or array in the object of the called method or the object of the caller.
    The former case saves space, the later may be better if ther are many callers and they don't want to be sharing the same returned data area.
    In the former case you only have to pass a pointer to the vars as a parameter and may not need to use the reurn value at all.
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-02-26 10:31
    How about that?
    Main.spin
    OBJ
      io:    "CogOS_IO(Term)"
      subf:   "SubFlexTest"
    
    VAR
      long    ret_struct[ subf#RET_STRUCT_SIZE ]
      
    PUB main
      subf.doSomething( @ret_struct, 10 )
    
      io.dec( ret_struct[ subf#XPOS ] )
      io.dec( ret_struct[ subf#YPOS ] )
      io.dec( ret_struct[ subf#WIDTH ] )
      ...
    
    and
    SubFlexTest.spin
    CON
      #0, XPOS, YPOS, WIDTH, HEIGHT
      RET_STRUCT_SIZE = 4
    
    PUB doSomething( retStructAddr, par1 )
      long[ retStructAddr ][ XPOS ] := 10
      long[ retStructAddr ][ YPOS ] := 10
      long[ retStructAddr ][ WIDTH ] := par1*3
      long[ retStructAddr ][ HEIGHT ] := par1*3
    

    This decouples things a little better. You can change (extend) the object without having problems in the Main ... even if the new parts of a return structure are not used there. Might make sense to have more than one arrays, at max. one for longs one for words and one for bytes.

    Another level would be to only use
    long ret_struct[ subf#RET_STRUCT_SIZE ]
    in the main and the way of how this buffer is filled is completely implemented in the objects code. It simply provides getter functions which need the address of the ret_struct, but take care of returning the right value out of this structure.
  • LawsonLawson Posts: 870
    edited 2012-02-26 11:17
    Not exactly what you're asking for, but it's worked well to make "get" methods whenever I want to let the parent object read variables.
    pub get(index)
    '' get a value from the data array
      index := 0 #> index <# ((@end_of_vars - @start_of_vars) ~> 2)            'bounds check
      result := long[@start_of_vars][index]              'return the correct value
    

    Having object.get(x) is a bit cryptic in the parent code but allows for quick changes. It also allows using an array to define what variables to look at. (i.e. for serial logging)

    Lawson
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2012-02-26 12:21
    Sometimes it's nice to be able to include function calls in expressions, even though the function computes two or more values that cannot be computed independently of each other. One way around this is to store all the results internally for later recall, viz:
    VAR
    
      long temperature, pressure
    
    PUB temp(new)
    
      if(new)
        compute_temp_press
      return temperature
    
    PUB press(new)
    
      if (new)
        compute_temp_press
      return pressure
    
    PRI compute_temp_press
    
      'Read the sensor and compute temperature and pressure from the reading.
    

    Whenever new is non-zero, that signals that you want a new computation instead of the value previously computed.

    Another way to do it, without requiring new is to assume that sequential requests for the same result mean to acquire a new reading, viz:
    VAR
    
      long temperature, pressure
      byte new_temp, new_press
    
    PUB temp
    
      ifnot (new_temp)
        compute_temp_press
      new_temp~
      return temperature
    
    PUB press
    
      ifnot(new_press)
        compute_temp_press
      new_press~
      return pressure
    
    PRI compute_temp_press
    
      'Read the sensor and compute temperature and pressure from the reading.
      new_temp~~
      new_press~~
    

    -Phil
Sign In or Register to comment.