• User defined types #1: Introduction

    Target audience: beginner, applicable version: 1.9.16.16 and newer

    ThinBASIC is a computer language with roots set in BASIC. The original BASIC did not ask the user to declare any type for the variables, it simply stored them as a number using 30 bits of precision.

    Many of the modern computer languages (Lua, Python, Ruby...) try to mimic this design by hiding the internal variable storage details from the programmer. This approach has the clear advantage of keeping things simple. Number is a number, text is a text. Programmer focuses on the problem instead of implementation. So far so good.

    The possible controversy of this approach starts to appear once you realize the program needs to run on physical device whose resources are limited. The mentioned approach of variable complexity hiding also poses two issues as well for people who would start with such a language as with their first programming language:

    • the correlation between program memory usage and variables is unclear
    • performance characteristics can vary surprisingly, as the language switches the backends and performs the memory reallocations

    ThinBASIC takes different route.

    Numeric data types

    With our interpreter, the choice of numeric data type is responsibility of the programmer. While the behavior of other solutions could be mimicked by simply using the Number data type, which poses the ability to hold the largest range of values thanks to 80bits of precision, once we move from playful stage to work on real world solutions, using specific types can bring great efficiency advantages.

    ThinBASIC, like some other programming languages, offers two kinds of numeric types: integer and floating point. Integer types should be the type of choice whenever you use whole numbers. They are further divided to signed and unsigned.

    In case you can afford it memory wise and the range of values is enough for your task, the best size/performance ratio in thinBasic is provided by the Long data type, which can be also referenced by its alias Int32. It is is signed, 32-bit integer. This is the reason why you may see Long being used even in cases, where you would expect DWord (or UInt32) otherwise. For example as counters in for loops, which usually go from 1 up.

    String data types
    Similar level of fine control is given to String data types. While String itself will do fine in most of the cases, it has its drawbacks. Did you know, for example, that each time you assign value, append or otherwise alter the length, a memory reallocation is performed?
    Code: [View]
    uses "console"
    
    string myText
    
    printL "Variable address: | Data address:"
    printMemoryInfo(myText)
    
    myText = "Earth"
    printMemoryInfo(myText)
    
    myText = "Venus" ' Same length, no change
    PrintMemoryInfo(myText)
    
    myText = "Earth and Moon"
    printMemoryInfo(myText)
    waitkey
    
    function printMemoryInfo(byRef stringVariable as string)
      string stringVariableAddress = hex$(varPtr(stringVariable))
      string stringDataAddress     = hex$(strPtr(stringVariable))
      
      printL lSet$(stringVariableAddress, 20 using " ") +
             lSet$(stringDataAddress, 20 using " ") +
             stringVariable  
    end function
    Copy and run on your PC to see the effect.

    This is the price you pay for String having virtually unlimited length. If this is unacceptable for you, performance wise, you can address the issue via 2 approaches.

    The first one is more strict and means you allocate the maximum string size ahead. You can do so during the declaration:
    Code: [View]
    uses "console"
    
    dim myText as string * 14
    
    printL "Variable address: | Data address: | Content:"
    printMemoryInfo(myText)
    
    myText = "Earth"
    printMemoryInfo(myText)
    
    myText = "Venus" ' Same length, no change
    printMemoryInfo(myText)
    
    myText = "Earth and Moon"
    PrintMemoryInfo(myText)
    waitkey
    
    function PrintMemoryInfo(byRef stringVariable as string)
      string stringVariableAddress = hex$(varPtr(stringVariable))
      string stringDataAddress     = hex$(strPtr(stringVariable))
      
      printL lSet$(stringVariableAddress, 20 using " ") +
             lSet$(stringDataAddress, 20 using " ") +
             stringVariable  
    end function
    Copy and run on your PC to see the effect.

    Of course, it is not possible to anticipate the maximum size in all the scenarios, and that is where more complex systems, such as string builders come to play.
    I prepared one such for thinBasic, and you can download it and use in your projects.

    User defined types
    Imagine a situation. You are about to create a game and you need to store data about a ship cruising through the space. What do we need - position, rotation, maybe health, shields and ammo, right? The data model of this could end up as something like:
    Code: [View]
    number positionX, positionY, rotation, health, shields, ammo
    Would this work? Sure. Now make it for two players:
    Code: [View]
    number positionX(2), positionY(2), rotation(2), health(2), shields(2), ammo(2)

    Eh... nice try. But what about wrapping it in more descriptive user defined type?

    Code: [View]
    type Starship
      positionX as number
      positionY as number
    
      rotation  as number
    
      health    as number
      shields   as number
    
      ammo      as number
    end type
    With such a declaration, you can prepare two ships with same set of properites as easily as:
    Code: [View]
    dim player(2) as Starship
    How big is our single ship in memory then?
    Code: [View]
    printl sizeOf(Starship)

    60 bytes. 60 bytes! Really?!

    This is where we are hitting what we talked in the beginning - not every property needs to be 80bit, right? In this case, none of them.
    The transformational values as well as health and shields can be easily 32bit and for ammo we can restrict it to something like 16bit unsigned integer.
    Where do these estimations come from? From simply looking up the table and knowing what the fields will be used for.

    So after facelift:
    Code: [View]
    type Starship
      positionX as single
      positionY as single
      
      rotation  as single
      
      health    as single
      shields   as single
      
      ammo      as word
    end type


    ...and voila! We are suddenly at 22 bytes per ship, that is almost 1/3 of the original size.

    Conclusion
    We are living in 21st century, we have computers with huge amounts of RAM. That does not mean we should waste it, remember me the next time you will shutdown your browser from task manager Be proud to be in control of the situation, and use the thinBasic language resources to your advantage.

    Today we learned that user defined types can be of great help to logically group your variables together, to easily create copies of data models and measure their memory footprint. Stay tuned for more, as there is a lot more of exciting stuff to learn!
    Comments 2 Comments
    1. kcvinu's Avatar
      kcvinu -
      Nice tutorial. Thanks.
    1. ReneMiner's Avatar
      ReneMiner -
      Great!
      I had to return the ball you played here and make a step into introducing functionality of udts, so it becomes visual. To keep it simple there's no shooting nor networking

      ' #Filename "UDT_Functions_Example1.tBasic"
      Uses "TBGL"
      
      Begin Const
        ' window-size:
        %Width       = 300
        %Height      = 300
        ' window-handle:
        %hWnd        = TBGL_CreateWindowEx(                 _
                       "UDT-Functions-example 1  [esc to exit]", _
                       %Width, %Height, 32,                 _
                       %TBGL_WS_WINDOWED | %TBGL_WS_CLOSEBOX | %TBGL_WS_DONTSIZE )
        ' size of ships & font               
        %Size        = 20
      End Const
      
      ' --------------------------------------------------------------------------------------------------
      Function TBMain()
      ' --------------------------------------------------------------------------------------------------
      
       
        ' --- clear keyboard-buffer
        TBGL_ResetKeyState()
        ' --- display the window
        TBGL_ShowWindow
      
        ' --- create a font at slot 1:
        TBGL_BuildFont(TBGL_FontHandle("Courier New", %Size, %TBGL_BOLD), 1)
       
        ' --- simple TBGL 2D-Setup:
        TBGL_UseDepth FALSE            ' draw 2D only
        TBGL_DepthFunc(%TBGL_ALWAYS)   ' draw from back to front
        
        
        Local i, cx, cy As Long
       
        ' --- find the window-center:
        cx = %width  * 0.5 
        cy = %height * 0.5
       
        ' --- create a few players starships:
        Local player(4) As starship
       
        
        ' --- setup individual players data: 
                       ' name ,  pos X , pos Y  , rot,  dir ,  R ,  G ,  B
        player(1).Setup "Mike", cx - 80, cy - 80,   0,  1.25, 255,   0,   0
        player(2).Setup "Eros", cx + 80, cy - 80,   0,  -1.5,   0, 255,   0
        player(3).Setup "Jose", cx - 80, cy + 80, 180,  -1.0,   0,   0, 255
        player(4).Setup "Petr", cx + 80, cy + 80, 180,  1.75, 255, 255,   0
        
        ' --- Main-Loop
      
        While TBGL_IsWindow(%hWnd)
          
          TBGL_ClearFrame
            ' --- for each player: 
            For i = 1 To CountOf(player)
            ' --- call to draw the starship:
              player(i).Render
            Next
                
          TBGL_DrawFrame
      
          ' --- check for input:
          If TBGL_GetWindowKeyState(%hWnd, %VK_ESCAPE) Then Exit While
      
        Wend
      
      End Function
      
                      
      
      ' ##################################################################################################
      Type Starship
      ' ##################################################################################################
        positionX As Single
        positionY As Single
         
        rotation  As Single
         
        ' health    As Single   
        ' shields   As Single
        ' ammo      As Word 
        
        '     nay, we don't shoot yet...  
        
        '     let each have a different name, direction & color instead:
         
        sName     As String
        direction As Single   ' i.e. direction & speed of rotation
        
        R         As Byte     ' color-values
        G         As Byte
        B         As Byte
      
      ' --------------------------------------------------------------------------------------------------
      ' function whithin Type ... End Type will apply to the parenting UDT:
        Function Setup( ByVal sName      As String, _
                        ByVal posX       As Single, _
                        ByVal posY       As Single, _  
                        ByVal rotation   As Single, _
                        ByVal direction  As Single, _
                        ByVal R          As Byte,   _
                        ByVal G          As Byte,   _
                        ByVal B          As Byte    )
      ' --------------------------------------------------------------------------------------------------
          ' this will setup values of the calling starship
          
          ' --- keyword "Me" tells thinBasic to use 
          '     the UDT-variable which called this function
           
          Me.sName     = sName
          Me.positionX = posX
          Me.positionY = posY  
          Me.rotation  = rotation
          Me.direction = direction   
          Me.R         = R
          Me.G         = G
          Me.B         = B
      
        End Function
                                                                                        
      ' --------------------------------------------------------------------------------------------------
        Function Render()
      ' --------------------------------------------------------------------------------------------------
          ' this will draw the calling players starship
          
            
          ' --- lets rotate Me:
          Me.rotation += Me.direction
          
          ' --- keep my rotation in range from 0 to 360: 
          If Me.rotation < 0 Then
            Me.rotation += 360
          ElseIf Me.rotation > 360 Then
            Me.rotation -= 360
          EndIf
          
              
          ' --- now render Me:
          
          TBGL_PushMatrix 
            ' --- use my individual position and rotation:
          
            TBGL_RenderMatrix2D(0, %Height, %Width, 0)
            TBGL_Translate Me.PositionX, Me.PositionY
            TBGL_Rotate Me.Rotation
            
            ' --- draw the ship:
            TBGL_BeginPoly %GL_TRIANGLES 
              ' use individual color:
              TBGL_Color Me.R, Me.G, Me.B
              ' a starship be some triangle defined by 3 points:
              TBGL_Vertex  0, -2 * %Size       ' top center
              TBGL_Vertex  %Size, %Size        ' lower right
              TBGL_Vertex -%Size, %Size        ' lower left
              
            TBGL_EndPoly   
          TBGL_PopMatrix
          ' --- print my name inverted on top:  
          TBGL_PushLogicOp %TBGL_DST_INVERTED  
            TBGL_PrintFont2D Me.sName, Me.PositionX, Me.PositionY, %TBGL_ALIGN_NONE, %TBGL_ALIGN_CENTER_DOWN
          TBGL_PopLogicOp
         
        End Function  
      ' ..................................................................................................
      End Type
      ' ..................................................................................................
      
      There's to add that UDT-Functions of course can return results - even if not shown here