Shop OBEX P1 Docs P2 Docs Learn Events
Newbie Question: Sharing common code/data/objects among spin files? — Parallax Forums

Newbie Question: Sharing common code/data/objects among spin files?

Roger MilneRoger Milne Posts: 11
edited 2009-09-29 14:16 in Propeller 1
After getting a good amount of code running, I am finding that I'm looking for a little more structure in it now.·

There are several kinds of things I'd like to organize, some of which I've figured out, and some of which I have not.

1) Spin Code
2) Assembly Code (like a file full of handy subroutines)
3) Data
4) Constants

I've figured out a few of them already:

1) Spin code from a different .spin file, is callable once you·mention·the other .spin file in an OBJ·section.· Then you can do something like theObject.TheSpinMethod(parameters)
4) You can access another .spin file's constants by saying theObject#TheConstant, so-long as you've mentioned the object in an OBJ section.

...now comes the tricky part that I'm actually trying to solve right now...

I have a .spin file with spin methods in it, which two other spin files both wish to call.· However, I'm afraid that, should I mention·this common file·in both other spin files' OBJ sections, I'd create two separate instances of it.· Am I correct?· How do I avoid this happening?· I am assuming this, because I see a jump in my variables-used, when I mention a supposedly common component from a second spin file.

If I have two components (the mainline, and a tile-renderer), and both of them want to access the spin functions of a single instance of the LCD component, how do I structure the code?

Thanks!!!

··· Roger
·

Comments

  • StefanL38StefanL38 Posts: 2,292
    edited 2009-09-28 04:47
    Hello Roger,

    each instance of an object has its own variables. The methods are compiled only ONCE. You did see an increase of variable-space but no increase of the code-space.

    Now if you want to avoid doubling the variables you could define them in the DAT.section. BUT: then it is up to you to control that there is no access-colliding
    especially when two different cogs are calling and writing a value to a variable.
    You would have to implement some kind of a lock-mechanism that variables are locked as long as one method is calling them.
    Otherwise you could get interfering phenomens that the value of a variable changes inbetween

    example

    method A starts
    variable X has value 100
    method A does a first part calculating something with value 100

    now method B starts and changes X to 200

    after that method A goes on doing some more but now with value 200
    where it should be still 100

    A lock mechanism will need extra code and therefore needs RAM to.
    The lock mechanism would force method B to wait until method A has finished
    Another idea could be to add the code to one of the two others.

    Sometimes it is good to have a shared buffer where different cogs can WRITE into it
    and a third cog is only READING the buffer.

    If this is a clever way depends on what you want to do. What are your mainline and tilerender doing ?
    Are they running in different cogs ?
    Do they Just WRITE to the Display ?

    In this case a buffer could be suitable.

    best regards

    Stefan
  • Roger MilneRoger Milne Posts: 11
    edited 2009-09-28 05:30
    Thanks for replying smile.gif

    The Lcd component (.spin file) offers methods that essentially issue commands to another COG. That other cog, just performs async execution of those commands. The commands are, for example: AsyncWriteLcdCommand, AsyncWriteByte, AsyncWriteBytes, AsyncWriteParameter, AsyncReadByte, etc. There's also a way to WaitUntilCommandComplete. The COG knows how to talk to the LCD controller and do what is needed, and signal completion when done. Much like the graphics demo code works, as I understand. Tight little loop waiting for a command to appear in a variable, for execution. Null-it-out to indicate completion.

    The mainline may send a few LCD commands, in order to initialize the LCD device into a specific video-mode, or paint a certain region of the screen by issuing an AsyncWriteBytes.

    The TileRenderer code, can be configured to paint a rectangular sub-section of the screen, with 8x8 blocks of pixels. So, this .spin file will have another COG running around re-thinking exactly what a cell looks like, and ultimately, calling the LCD's WriteBytes command to cause the cell to be uploaded to the LCD Display. At the moment, this is written in spin, so I can still call the LCD's command methods easily.

    I definitely realize I'll eventually need to use some mutexes to control access to the LCD between the mainline and the TileRenderer.

    I'll also eventually need to share the address of the LCD command variable, with the TileRenderer, so it can write directly to it, once it's all written in assembly, right? There's no calling back from assembly, to spin, is there...

    The only var section in the LCD component, is the command and parameter variables used to communicate between the spin code and the assembly code.

    I just had a realization... If the mainline is running on a COG, and then the TileRenderer is running on a different cog, then they both call a method in the LCD component, then they will both manipulate different instances of the VAR section. Only one of those instances is actually being watched by the LCD COG! This is no good!! smile.gif

    So, if I understand what you're saying, I'm going to need to go through the LCD component, and eliminate the VAR section, in favour of a DAT section. That's going to be tricky, as the only VAR section is the one that has the shared variables between the spin methods and the assembly code. Doesn't it have to be VAR? When I change it to DAT, I cannot assign values into that variable anymore, from spin - unless I'm missing something?
  • SamMishalSamMishal Posts: 468
    edited 2009-09-28 09:30
    Hi Roger,
    Let me see if I can explain this easily.
    I understand you want to use the LCD driver from multiple objects. If you declare the LCD driver·as a sub-object in each one you would incur the problem of having too many cogs wasted since each instance would start its own cog.
    Another way is what Stefan is suggesting. This entails a LOT of changes you have to make to the LCD object so that it will use DAT instead of VAR and also to make it ensure that it runs itself in one cog etc. This requires you to know and understand the LCD object well.
    This is·a good·way to do what you want. HOWEVER, here is an easier workaround that you can do EASILY.
    Make a new object. In this object·instantiate the LCD object. Also give it a start method that takes care of starting the LCD object. Also in this object make methods that do nothing more than call the LCD object's methods. Basically these methods are a·CONDUIT to the LCD object's methods. This object should not use VARs, instead use DATs for all variables you may need for this new object. There should not be any maybe just one. Let’s call this new object LCD_Wrapper.Spin
    Now in your project where you need to use the LCD object instead of instantiating the LCD object instantiate the LCD_Wrapper object.
    You have to make sure that the FIRST ever object to use the wrapper will call the Start() method of this wrapper. HOWEVER....no other object needs or should call this start method. ONLY ONCE.
    In you various objects....instead of calling the LCD methods as you would have normally done...call the wrapper's methods.
    So now you have used the LCD object only once and it should work as normal and you do not need to do any changes to it at all.
    Also you have insured the LCD object is started properly and only once since it was called through the wrapper's start method only once. Remember you called the wrapper's start method only once and that calls the LCD object's start method.
    Now all other objects that use the LCD object are not actually seeing it or calling it at all. They are ALL calling the wrapper's methods. The wrapper takes care then of calling the LCD object's methods. So the wrapper acts as a WRAPPER and CONDUIT to the LCD object.
    Since the wrapper does not use VARs then there is no overhead of having it instantiated many times. Also even though it is instantiated multiple times its code is only included into the project once (SPIN is good this way).
    The beauty of this whole thing is that now the wrapper when·instantiated MANY times gives you no overhead except for the small code of the methods that do nothing more than call the methods of the LCD object. AND this object does not use cog's so no cog waste either. THUS...you can safely instantiate it many times without worries.
    JUST REMEMBER....call its start method only from ONE of the calling objects. Figure out which calling object will require to use the LCD object FIRST and in that one object call the wrapper's start method to insure that the LCD start method is called. NO OTHER object should call the wrapper's start method. BUT...all objects can now call the CONDUIT method's of the wrapper to effect actions that the LCD object will need to do.
    Actually....all designers·of objects that are likely to be required multiple times in a project should really provide·a wrapper object for their objects and save the user the trouble of designing one like you will have to do for the LCD object.....I hope this catches on.....
    The only problem I can see is that you have to go through the LCD object and figure out all the Public methods and duplicate their calling names and parameters.
    I will give you an example.
    Let’s say (this is just my wording here) that the LCD object has a public method that looks like this
    Pub Start(param1, param2...):returnvar|localvar,localvar
    ··· code
    ··· code

    Pub SomeMethod(param1,param2):returnvar|localvar1,localvar2
    ··· code
    ··· code

    Now you need to provide in your wrapper object the following public methods
    Obj
    ·· LCD: "Lcd_object"·· 'just an example of how the LCD object is doen in the wrapper object
    ····························· 'use your own names etc.

    Pub Start(param1,param2....)
    ·· return LCD.Start(param1,param2...)
    ·· 'that is all no other code or loacal variables etc.
    ·· 'this start() method should only be called once from the first parent object that
    ·· 'ever is likely to use the warapper.....

    Pub SomeMethod(param1,param2)
    ··· return LCD.SomeMethod(param1,param2)
    ··· 'that is it no more code or variables nothing else

    Notice that in the wrapper CONDUIT method you do not need to declare return variable names nor local variables etc. Notice how it just passes the parameters through to the LCD object's method of the same name and notice how it returns the return value.
    IT IS JUST A CONDUIT....passer through of the parameters and of the return value.
    Do this to all the PUB methods and that is all....PRI methods are not usable of course outside any object anyway...so need for CONDUITS for them.
    I hope this is clear.
    Wrapper's like this would work for things like the FullDuplexSerial and other objects that usually are required to be used in multiple sub objects of a project and they have the trouble of requiring cogs.
    There is one caveat to all this. You cannot use the CONDUIT methods SIMULTANEOUSLY.......i.e. if you are instantiating the wrapper object in objects that run in multiple cogs then you run the risk of two cogs trying to use the wrapper object SIMULTANEOUSLY....this is not good.
    One way to prevent this is to use LOCKS...(SEMAPHORES) that ensure that this is not done and to MANAGE the situation....I do not want to go in to the complexity of this here. BUT......the wrapper makes it easier to handle this problem than doing it in the LCD object itself.
    For now just make sure that you design your program so that this will not happen. OR....go through the CONDUIT methods and make use of LOCKS (semaphores) to handle the situation by making other methods wait their turn etc…..this is a complex subject.
    Regards
    Samuel
  • Roger MilneRoger Milne Posts: 11
    edited 2009-09-29 01:51
    Thanks SamMishal, the wrapper system is a great and flexible solution to this problem for a lot of cases! I see that it's the perfect spot for inserting sempahores for managing access, as well.

    For others who are reading this thread, I also got to the bottom of the VAR -> DAT conversion mystery.

    I was trying to convert my VAR variables to DAT variables, while also using RES to reserve space for them. Apparently, this does not work.

    Here's an example... If I have:

    VAR
    long lcdJob
    long parameter

    ...and I also have this spin method:

    PUB Initialize
    lcdJob := CMD_Idle
    CogNew( @LcdCommunicationsLoopAsm, @lcdJob )

    So... I remove the VAR section, and replace it with the following (at the bottom of the file)...

    DAT
    lcdJob RES 1
    parameter RES 2

    When I compile the code, the Initialize method fails to compile, as it cannot find the 'lcdJob' variable for assigning anymore. This was my big mystery...

    I finally figured out what I was doing wrong.

    DAT
    lcdJob long 0
    parameter long 0,0

    ...WILL WORK! For some reason, it appears that you cannot declare the variables using RES, you must declare them using a data-type, such as 'long', which also forces you to give initial values (not a bad thing).

    Hope that helps others...
  • Ken PetersonKen Peterson Posts: 806
    edited 2009-09-29 13:38
    SamMishal: I like your wrapper concept. I have been modifying existing objects to use a Singleton pattern (variables in the DAT section), but I hadn't thought of just creating a wrapper.

    Your caveat about the start method only being called once is easily solved by keeping a state variable and only doing the initialization if it hasn't already been done. Then everyone can call the start() method with no worries.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    "I have not failed. I've just found 10,000 ways that won't work. "
    - Thomas A. Edison
  • SamMishalSamMishal Posts: 468
    edited 2009-09-29 14:16
    Ken Peterson said...
    Your caveat about the start method only being called once is easily solved by keeping a state variable and only doing the initialization if it hasn't already been done. Then everyone can call the start() method with no worries.

    Hi Ken,
    ·
    I LIKE THAT too....good idea.......We should disseminate this idea more ...no?

    I think it makes life simpler for doing the LOCKs too and overall it is a more logical and
    organized way of using a cog-based object.

    Note: the state variable should be in a DAT section so that it would not use up RAM every time the wrapper object is
    instantiated. The wrapper object should ideally use NO VARs for the same reason since it is going to be instantiated
    many times. However the main object to be wrapped can use VARs and there is no need to change all the existing objects
    e.g.·FullduplexSerial since they will only be ever instantiated just once inside the wrapper object.

    Regards
    ·
    Sam

    Post Edited (SamMishal) : 9/29/2009 2:31:25 PM GMT
Sign In or Register to comment.