Shop OBEX P1 Docs P2 Docs Learn Events
Is there an object pointer in Spin2? — Parallax Forums

Is there an object pointer in Spin2?

Ive been developing a GUI framework for the P2 for a project, and I’ve had to get creative with method pointers to be able to reuse one object’s methods across all other objects. It works but code is getting too cumbersome and I have too much duplicated code. A pointer to an imported object that could be passed around to other objects so they can call any of the object’s methods would really help, but I have not found it in the Spin2 manual. Does this exist? If so, is there an example somewhere?

Comments

  • evanhevanh Posts: 15,914

    Look up Method Pointers in the Spin2 manual - https://docs.google.com/document/d/16qVkmA6Co5fUNKJHF6pBfGfDupuRwDtf-wyieh_fbqw/
    I think that's what you're wanting.

  • @jrullan said:
    ... I’ve had to get creative with method pointers to be able to reuse one object’s methods across all other objects. It works but code is getting too cumbersome and I have too much duplicated code. ....

    Thanks Evan, but that's exactly what I'm using right now and it works, but I think that an object's reference would be much more powerful and simpler from the programmer's perspective. Right now in order to pass services from an object to another, you must define those specific method pointers, which is fine for one or two methods, but when you're still developing the "service" object and you need to add one more service (method) you need to edit all method calls that pass the method pointers across all objects that require its service. It would be much simpler to pass the object's pointer and call the object's methods using it's reference.

    I understand Spin2 is very different from C or Python, but if it were possible it would open up many possibilities and IMHO it would produce clearer and cleaner code.

  • @jrullan
    I don't quite understand what your problem is.
    You can include an object in several objects. The object methods are always the same. If you move the variables into a DAT section, they are also the same for all inclusions. Only VAR sections are extra for each inclusion. At least as far as I have understood.

  • At one time Chip had proposed object pointers using syntax like:

    OBJ
       AbstractObject = "theObj"
    ...
    PUB check(x)
      AbstractObject[x].CallMethod()
    

    where the AbstractObject[x] casts the pointer x to an abstract object. I don't think this actually made it into the PNut compiler though. FlexSpin does implement it.

  • @dnalor said:
    @jrullan
    I don't quite understand what your problem is.
    You can include an object in several objects. The object methods are always the same. If you move the variables into a DAT section, they are also the same for all inclusions. Only VAR sections are extra for each inclusion. At least as far as I have understood.

    Well I'm using the LCD_Graphics_Driver for the ILI9341 from ManAtWork and Clusso99. The driver provides the primitive drawing functions that I need to use in every UI element object so they can draw themselves. I might be missing something but I see two possible approaches:

    1) I could import the driver object in every UI element object and call it's drawing methods to be able to graphically represent the UI element
    2) Or I could pass a pointer to the driver object to every UI element during their initialization as a parameter

    I'm not sure about #1 because I think the driver was meant to be included once. I don't know how it will behave if I call multiple instances of it from different places.

    Because it doesn't seem to be possible to do #2, I opted instead to pass a method pointer for every drawing method I think I will need to every UI element object on their initialization. This is what I am actually doing but it feels like a workaround to something more straight forward like a reference to the driver object itself as a singleton.

    To clarify, I'm still learning all things Propeller 2 and I am coming from a C/C++ background where you can pass addresses of objects around and dereference them as you would with the address of a Spin2 method pointer.

  • evanhevanh Posts: 15,914

    @jrullan said:

    @jrullan said:
    ... I’ve had to get creative with method pointers to be able to reuse one object’s methods across all other objects. It works but code is getting too cumbersome and I have too much duplicated code. ....

    Thanks Evan, but that's exactly what I'm using right now ...

    Ha, oops. I read it but didn't sink in at the time.

  • jrullanjrullan Posts: 168
    edited 2022-11-28 21:20

    @ersmith said:
    At one time Chip had proposed object pointers using syntax like:

    OBJ
       AbstractObject = "theObj"
    ...
    PUB check(x)
      AbstractObject[x].CallMethod()
    

    where the AbstractObject[x] casts the pointer x to an abstract object. I don't think this actually made it into the PNut compiler though. FlexSpin does implement it.

    Thanks @ersmith.
    I've been trying to stick to Propeller Tool, but this could sway me...
    It might not be an everyday feature but it is definitely an improvement over method pointers and for cases like mine it makes much more sense.

  • @dnalor got it right.

    You can include a object into multiple objects in Spin and use as usual.
    The compiler will prune out multiple copies and just keep one, thus building your object pointers by itself.

    The way it is thought of is that multiple instances will share the code and DAT Sections, but each instance has own VAR sections.

    If you do not need VARs independent for each copy, move the definition into the DAT section.

    Mike

  • @msrobots said:
    @dnalor got it right.

    You can include a object into multiple objects in Spin and use as usual.
    The compiler will prune out multiple copies and just keep one, thus building your object pointers by itself.

    The way it is thought of is that multiple instances will share the code and DAT Sections, but each instance has own VAR sections.

    If you do not need VARs independent for each copy, move the definition into the DAT section.

    Mike

    Thanks @msrobots for the clarification. It makes sense. This should be documented somewhere as a "best practice".

  • evanhevanh Posts: 15,914

    Hmm, I've not used that either, yet likely would have. It does feel a little counter-intuitive.

  • You are using this one 'https://github.com/parallaxinc/propeller/blob/master/libraries/community/p2/All/ili9341/LCD_ILI9341_XPT2046.spin2' ?
    It has a VAR section with e.g. width, height. So including more than once is rather not possible without changes. So moving it to DAT (like michael wrote) and calling 'Start' only from once, not in each object could work.

    Just an idea, not sure if this would work:
    Now you pass method pointers to each object you want to use the methods. Maybe you could make a wrapper-object, where you pass the method pointers to a 'start'-method which saves this pointers to DAT. In this objekt you can then use this pointers in wrapper-methods. Then you include this wrapper-object wherever you need it and call the wrapper methods: e.g: wrapper.DrawIcon (imagePtr, w, h) and in this method you then call pointer(imagePtr, w, h).

  • RaymanRayman Posts: 14,640

    @ersmith said:
    At one time Chip had proposed object pointers using syntax like:

    OBJ
       AbstractObject = "theObj"
    ...
    PUB check(x)
      AbstractObject[x].CallMethod()
    

    where the AbstractObject[x] casts the pointer x to an abstract object. I don't think this actually made it into the PNut compiler though. FlexSpin does implement it.

    @ersmith I don't understand how this works... Does there happen to be a more complete example?
    I just looked at the help and I see that the "=" here is different than the usual ":".
    But, I'm not getting how to actually use this. Guess I don't understand how "x" is defined.
    Also, don't see where the actual CallMethod() code and variables are...

  • @Rayman : Suppose a sub-object wants to print some stuff to the serial port using its parent's serial object. Then it would do something like:

    ' SubObject.spin2
    OBJ
       fds = "FullDuplexSerial" ' abstract object, does not create storage
    
    PUB mymethod(s) ' just a dummy method for now; s is the serial object to print with
       ' do some stuff
       ' now print a message
       ' s is treated as a pointer to a FullDuplexSerial object
       fds[s].str(string("Message from sub-object", 13, 10))
    

    In the top you'd do something like:

    OBJ
       o: "SubObject"
       ser:  "FullDuplexSerial" ' really allocates storage
    PUB main()
       ser.start(31, 30, 0, 115200)
       o.mymethod(@ser)  ' pass ser to the sub-object to print with
    
  • RaymanRayman Posts: 14,640

    @ersmith thanks! Think I’m starting to get it…

  • Thanks Eric, this is very useful! I've nedded this several times. For simple objects like FullDuplexSerial there is a solution called SerialMirror which has all the variables in a DAT section and therefore behaves like a static object in C. But for more complex objects like the LCD driver it is better beiong able to do that without modifying the source.

  • It's been a while since I posted this question but over the weekend I finally took a deep breath and decided to try out porting my GUI framework over to VSC/FlexSpin to try out this Object Pointer feature.

    It is fantastic! With it I can't think of anything I cannot accomplish in this project with Spin2 , ATM.

    Thanks @ersmith for all the hardwork and for adding this feature to FlexSpin. My only complaint is that I don't have the FlexProp debugger with VSC or I don't know how to enable it yet.

    OBJ
    'Abstract Objects
      BUTTON = "Button"
    
    ...
    
          BUTTON[registered_widget[selected_widget]].trigger_event()
    
    ...
    
          if widget_types[w] == BUTTON_TYPE
            BUTTON[registered_widget[w]].draw()         
    
    ...
    
    PRI redraw_previous_widget()
        if widget_types[previous_widget] == BUTTON_TYPE
          BUTTON[registered_widget[previous_widget]].draw()
          ...
    
    PRI highlight_button(ptr)|i, topleft, bottomright
      'Draw border around newly selected widget
      topleft, bottomright := BUTTON[ptr].get_coordinates()
      ...
    
    
  • I've ran into a similar issue when experimenting with I2C. I wanted to create a hardware abstraction layer for an I2C device, but it needed to reference a bus, not define it, since multiple devices would be attached to the same bus. A DAT section could work but I believe that introduces code "smell", and it isn't unrealistic to have multiple I2C busses. Eventually I'll switch to a 3rd party tool (e.g., FlexSpin) and end this suffering but figure I'd share my approach.

    My approach was an abstraction object that defined the API, and a service which did the logic to implement said API. Similar to modern high-level language "interface" type. I've pushed my (highly) development code to my git repo, piLibrary#feature/i2c-master. Specifically, piI2CMasterService, piI2CMasterBus, and Demos/Demo_I2CMaster. I'm not happy with the nomenclature yet though, I don't think it is generalized enough or self-explanatory.

    TL;DR;, basically you attach a bus to service that provides that API. Brief example:

    CON
      ' clock settings (adjust to the board being used)
      _xtlfreq                      = piBoard.XTAL_FREQ
      _clkfreq                      = 200_000_000
      IMU_ADDR                      = %11010110
    
    
    OBJ
      piBoard:      "piBspP2Eval"
      i2cService:   "piI2CMasterService"
      i2c:          "piI2CMasterBus"
      imu:          "LSM9DS1"
    
    
    PUB Main() | x, y, z
      i2cService.Start(8, 9, 100000)
      imu.Start(IMU_ADDR, @i2cService.AttachBus)
      imu.Init()
    
      debug(`SCOPE IMU SIZE 640 256)
      debug(`IMU 'x' -2200 2200 256)
      debug(`IMU 'y' -2200 2200 256)
      debug(`IMU 'z' -2200 2200 256)
    
      repeat
        x, y, z := imu.GetAccel()
        debug(`IMU `(x,y,z))
        waitms(5)
    
  • @DarkInsanePyro said:
    I've ran into a similar issue when experimenting with I2C. I wanted to create a hardware abstraction layer for an I2C device, but it needed to reference a bus, not define it, since multiple devices would be attached to the same bus. A DAT section could work but I believe that introduces code "smell", and it isn't unrealistic to have multiple I2C busses. Eventually I'll switch to a 3rd party tool (e.g., FlexSpin) and end this suffering but figure I'd share my approach.

    My approach was an abstraction object that defined the API, and a service which did the logic to implement said API. Similar to modern high-level language "interface" type. I've pushed my (highly) development code to my git repo, piLibrary#feature/i2c-master. Specifically, piI2CMasterService, piI2CMasterBus, and Demos/Demo_I2CMaster. I'm not happy with the nomenclature yet though, I don't think it is generalized enough or self-explanatory.

    TL;DR;, basically you attach a bus to service that provides that API. Brief example:

    CON
      ' clock settings (adjust to the board being used)
      _xtlfreq                      = piBoard.XTAL_FREQ
      _clkfreq                      = 200_000_000
      IMU_ADDR                      = %11010110
    
    
    OBJ
      piBoard:      "piBspP2Eval"
      i2cService:   "piI2CMasterService"
      i2c:          "piI2CMasterBus"
      imu:          "LSM9DS1"
    
    
    PUB Main() | x, y, z
      i2cService.Start(8, 9, 100000)
      imu.Start(IMU_ADDR, @i2cService.AttachBus)
      imu.Init()
    
      debug(`SCOPE IMU SIZE 640 256)
      debug(`IMU 'x' -2200 2200 256)
      debug(`IMU 'y' -2200 2200 256)
      debug(`IMU 'z' -2200 2200 256)
    
      repeat
        x, y, z := imu.GetAccel()
        debug(`IMU `(x,y,z))
        waitms(5)
    

    Looks like you are passing a method pointer. Am I right? The benefit of the object pointer is that once you have a hold on the object you want to use you can easily call its methods without explicitly setting the method pointers. You can then create setters and getters to manipulate the object internal variables which are not accessible and life is good again! :wink:

  • evanhevanh Posts: 15,914

    I only recently noticed I have been referencing object VARs from the parent in my code for a while now and not knowing that was special to Flexspin. So it seems Flxespin treats all VARs as public, while Proptool treats them as private.

    Maybe public/private declaration of VARs is a feature to be added to Spin2.

  • @evanh said:
    I only recently noticed I have been referencing object VARs from the parent in my code for a while now and not knowing that was special to Flexspin. So it seems Flxespin treats all VARs as public, while Proptool treats them as private.

    Maybe public/private declaration of VARs is a feature to be added to Spin2.

    In PropTool if you try to access an object var directly you get this

  • evanhevanh Posts: 15,914
    edited 2023-01-31 22:41

    Yes, Chip will have to make changes to Parallax's compiler to provide "public" VARs.

    Needless to say, I've been using Flexspin for a long time now. I hadn't realised I was using a unique feature until I tried testing with Proptool.

  • @jrullan said:
    Looks like you are passing a method pointer. Am I right? The benefit of the object pointer is that once you have a hold on the object you want to use you can easily call its methods without explicitly setting the method pointers. You can then create setters and getters to manipulate the object internal variables which are not accessible and life is good again! :wink:

    Yes, essentially a single method pointer to hook an "api object" onto a service. So, 1 call within an object to use all the methods available from a common service. From there, internally to the service and api object all the nitty-gritty stuff happens. So I can have a dozen I2C HAL objects (specific for each device & unique address) all able to talk over the single bus through these methods. So besides attaching an api to service, no redundant logic and only a single method pointer needs to be passed around.

    Flip-side, example of an OBJ consuming a service:

    OBJ
      i2c:          "piI2CMasterBus"
    
    
    VAR
      BYTE deviceAddress
    
    
    PUB Start(p_deviceAddress, i2cServiceAttach)
    
      deviceAddress := p_deviceAddress
    
      ' attach the I2C master bus service to the I2C api interface
      i2cServiceAttach(@i2c.SetMethodTable)
    
      ' initialize device
      'Init()
    
    
    PUB GetGyro() : x, y, z | word regx, word regy, word regz
    
      ' retrieve register data from IMU
      i2c.WriteStart()
      i2c.WriteByte(deviceAddress | 0)
      i2c.WriteByte(OUT_X_L_G)
      i2c.WriteStart()
      i2c.WriteByte(deviceAddress | 1)
      i2c.ReadBytes(@regx, 6, i2c.NACK)
      i2c.WriteStop()
    
      ' convert from words to return type of long
      x := regx SIGNX 15 * 2000 / 32768
      y := regy SIGNX 15 * 2000 / 32768
      z := regz SIGNX 15 * 2000 / 32768
    

    Quirky though, not going to pretend otherwise. :smile:

  • Object pointers seem like a great feature, but would it be the best way to accomplish something like the following:

    ' Top level app
    OBJ
        gui: "gui-functions"    ' <- I need methods like plot(), box(), etc in order to draw GUI things
        disp: "some.lcd.driver"    ' <- I have methods like plot(), box(), etc
    
    PUB main()
        gui.point_to_this_obj(@disp)    ' <- tell the gui who to talk to in order to draw GUI things (not syntactically valid, I know)
        gui.draw_a_button()    ' <- I use methods from disp to draw a button
    

    ...with the idea being some.lcd.driver could be swapped in for some.vga.driver, etc. and nothing else would have to change (provided some.vga.driver has the same interface).
    The way the examples in the above posts are written, it seems like gui-functions.spin would have to have an abstract instance of some.lcd.driver declared, so it'd have to be modified if you wanted to move to a different display driver.
    It almost seems like the way @DarkInsanePyro wrote it is the closest to what I'm trying to do. For a different project, it's in fact essentially how I did it, although my way was more "verbose" (I passed individual method pointers directly to the child object rather than pack them up).
    I hope that made sense...

Sign In or Register to comment.