Table of Contents
- Brief Summary of BrightScript Components
- BrightScript statements that work with BrightScript Component Interfaces
- Intrinsic types and object types
- Use of wrapper functions on intrinsic types
- BrightScript XML Support
- Garbage Collection
- Events
- Threading Model
- Scope
- Creating and Using Intrinsic Objects
- "m" The BrightScript "this pointer"
- Script Libraries
The BrightScript Component architecture and library are separate from BrightScript, but BrightScript requires them.
- All APIs exposed to BrightScript are exposed as BrightScript components. In other words, if a platform wants to expose APIs to be scripted, the platform must register a new BrightScript component.
- BrightScript has language features that are designed to work with BrightScript component Interfaces. These include: for each, print, the array operator, dot operator, and intrinsic objects.
- Fundamental BrightScript building blocks are implemented as BrightScript components. For example: Lists, Vector Arrays, Associative Arrays, and Objects.
Brief Summary of BrightScript Components
BrightScript Components are robust against version changes. In other words, scripts are generally backwards compatible with Objects that have undergone version improvements.
BrightScript Components keep a reference count and delete themselves when the reference count goes to zero.
A key BrightScript Component concept is the Interface. The term Interface is used here as it is in Java or Microsoft COM. An interface is a known set of member functions that implement a set of logic. In some ways an Interface is like a virtual base class in C++. Any script or C-compatible program can use an object's interface without regard to what type of object it is a part of, as long as it is familiar with a particular interface. For example, the roUrlTransfer component implements four interfaces: ifUrlTransfer, ifHttpAgent, ifSetMessagePort and ifGetMessagePort.
BrightScript statements that work with BrightScript Component Interfaces
For each
The for-each statement works on any object that has an ifEnum interface. These include: Array, Associative Array, List, ByteArray, and MessagePort.
Print
If the expression being printed evaluates to an object that has an ifEnum interface, print will print every item that can be enumerated.
In addition to printing the values of intrinsic types, PRINT will also print any object that exposes one of these interfaces: ifString, ifInt, ifFloat.
Wait
The wait function will work on any object that has an ifMessagePort interface.
Array Operator –"[]"
The array operator works on any object that has an ifArrayGet or ifArraySet interface. This includes Array, AssociativeArray, ByteArray, and Lists.
Member access operator "."
The "." Operator works on any object that has an ifAssociativeArray interface (as well as on any BrightScript Component (when calling a member function)). It also has special meaning when used on roXMLElement or roXMLList.
Expression Parsing
Any expression that is expecting an Integer, Float, Double, Boolean or String, can take an object with the ifInt, ifFloat, ifDouble, ifBoolean, or ifString interface.
Intrinsic types and object types
A variable may hold either an "intrinsic type" or an "object type". An intrinsic type is one of Integer, Float, Double, String, Invalid, Boolean or Function. An object type is one of Array, Associative Array or BrightScript Component. The most important difference is a variable containing an intrinsic type contains the value itself; that is, the Integer, Float, Double, etc. is contained within the variable. On the other hand, a variable containing an object type contains merely a pointer or reference to the actual object. Assigning an object type results in two pointers to the same object, while assigning an intrinsic type results in two copies of the same value, which can be modified independently of each other.
a = 42 ' a contains an intrinsic Integer b = a ' b contains a copy of a a = 43 ' does not modify b b = 44 ' does not modify a a = [ 1,2,3 ] ' a contains a reference to an array b = a ' b contains another reference to the same array a[0] = 5 ' now both a[0] and b[0] equal 5 b[1] = 6 ' now both a[1] and b[1] equal 6
The same thing holds true when variables are passed as function parameters. If the variable is intrinsic, the function parameter is a copy of the original. But if the variable is object, the function parameter is merely an additional reference to the same object. That is, intrinsic variables are "passed by value", while object variables are "passed by reference".
function Modify(a as Integer, b as Object) as Void a = 43 b.first = 6 end function ..... x = 42 y = { first: 1, second: 2 } Modify(x, y) ' now x is still 42 but y.first is 6
Each object maintains a "reference count", which is the number of variables which refer to the object. The reference count is set to 1 when the object is created and a reference to it is saved in a variable. When an object's reference count drops to zero, the object is destroyed.
a = CreateObject("roArray") ' array has a ref count of 1 b = a ' array has a ref count of 2 a = invalid ' array has a ref count of 1 (a no longer refers to it) c = b ' array has a ref count of 2 b = 100 ' array has a ref count of 1 (b no longer refers to it) c = invalid ' array has a ref count of 0 and is destroyed
Note that after the last statement, since no variables refer to the object any longer, it would be impossible to use the object even if it were not destroyed.
Use of wrapper functions on intrinsic types
If an intrinsic types is passed to a function that expects an Object, a "wrapper" object will be created, assigned the correct value, and passed to the function. Wrapper objects are objects which behave like the intrinsic entities that they represent. For example, an integer may be converted to an roInt object, which can be used much like an ordinary integer, but it has object semantics. This automatic conversion to an object is sometimes referred to as "autoboxing".
Function Main() MyFunA(4) MyFunB(4) End Function Function MyFunA(p as Object) as Void print "A",p,type(p) End Function Function MyFunB(p as Integer) as Void print "B",p,type(p) End Function
Will Print:
A 4 roInt
B 4 Integer
Print 5.tostr()+"th" ' prints 5th Print "5".toint()+5 ' prints 10 If type(5.tostr())<> "String" Then Stop If (-5).tostr()<>"-5" Then Stop If (1+2).tostr()<>"3" Then Stop If 5.tostr()<>"5" Then Stop i=-55 If i.tostr()<>"-55" Then Stop If 100%.tostr()<>"100" Then Stop If (-100%).tostr()<>"-100" Then Stop y%=10 If y%.tostr()<>"10" Then Stop If "5".toint()<>5 Or type("5".toint())<>"Integer" Then Stop If "5".tofloat()<>5.0 Or type("5".tofloat())<>"Float" Then Stop fs="-1.1" If fs.tofloat()<>-1.1 Or fs.toint()<>-1 Then Stop If "01234567".left(3)<>"012" Then Stop If "01234567".right(4)<>"4567" Then Stop If "01234567".mid(3)<>"34567" Then Stop If "01234567".mid(3,1)<>"3" Then Stop If "01234567".instr("56")<>5 Then Stop If "01234567".instr(6,"56")<>-1 Then Stop If "01234567".instr(0,"0")<>0 Then Stop
Note that
-5.tostr()
Will cause an error since the dot operator binds tighter than unary negation. Use:
(-5).tostr()
BrightScript XML Support
BrightScript supports XML via two BrightScript Components, and some dedicated language features. The BrightScript Component roXMLElement provides support for parsing, generating, and containing XML. In addition, the roXMLList object is often used to hold lists of roXMLElement, and implements the BrightScript standard ifList interface as well as the ifXMLList interface. Language features are provided via the dot operator, and the @ operator. BrightScript XML support is limited to UTF-8 encoded content.
Dot Operator
- When applied to an roXMLElement, the dot operator returns an roXMLList of children that match the dot operand. If no tags match, an empty list is returned.
- When applied to an roXMLList, the dot operator aggregates the results of performing the dot operator on each roXMLElement in the list.
- When used on XML, which is technically case sensitive, the dot operator is still case insensitive. If you wish to do a case sensitive XML operation, don't use the dot operator. Use the XML member functions.
Attribute Operator
The @ operator can be used on an roXMLElement to return a named attribute. It is always case insensitive (despite the fact that XML is technically case sensitive). When used on an roXMLList, the @ operator will return a value only if the list contains exactly one element.
For example, if the file "example.xml" contains the following:
<?xml version="1.0" encoding="utf-8" ?> <rsp stat="ok"> <photos page="1" pages="5" perpage="100" total="500"> <photo id="3131875696" owner="21963906@N06" secret="f248c84625" server="3125" farm="4" title="VNY 16R" ispublic="1" isfriend="0" isfamily="0" /> <photo id="3131137552" owner="8979045@N07" secret="b22cfde7c4" server="3078" farm="4" title="hoot" ispublic="1" isfriend="0" isfamily="0" /> <photo id="3131040291" owner="27651538@N06" secret="ae25ff3942" server="3286" farm="4" title="172 • 365 :: Someone once told me..." ispublic="1" isfriend="0" </photos> </rsp>
Then
rsp=CreateObject("roXMLElement") rsp.Parse(ReadAsciiFile("tmp:/example.xml"))
? rsp.photos.photo
Will return an roXMLList with three entries.
? rsp.photos.photo[0]
Will return an roXMLElement reference to the first photo (id="3131875696").
? rsp.photos
Will return an roXMLList reference containing the photos tag.
rsp.photos@perpage
Will return the string 100.
Use the GetText() method to return an element's text.
For example, if the variable booklist contains this roXMLElement:
<booklist>
<book lang=eng>The Dawn of Man</book>
</booklist>
then
print booklist.book.gettext()
will print "The Dawn of Man", and
print booklist.book@lang
will print "eng".
REM REM Interestingness REM pass an (optional) page of value 1 - 5 to get 100 photos REM starting at 0/100/200/300/400 REM REM returns a list of "Interestingness" photos with 100 entries REM Function GetInterestingnessPhotoList(http as Object, page=1 as Integer) as Object print "page=";page http.SetUrl("http://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=YOURKEYGOESHERE&page="+mid(stri(page),2)) xml=http.GetToString() rsp=CreateObject("roXMLElement") If Not rsp.Parse(xml) Then Stop Return helperPhotoListFromXML(http, rsp.photos.photo) 'rsp.GetBody().Peek().GetBody()) End Function Function helperPhotoListFromXML(http as Object, xmllist as Object, owner=invalid as dynamic) as Object photolist=CreateObject("roList") For Each photo In xmllist photolist.Push(newPhotoFromXML(http, photo, owner)) End For Return photolist End Function REM REM newPhotoFromXML REM REM Takes an roXMLElement Object that is an <photo> ... </photo> REM Returns an brs object of type Photo REM photo.GetTitle() REM photo.GetID() REM photo.GetURL() REM photo.GetOwner() REM Function newPhotoFromXML(http as Object, xml as Object, owner as dynamic) as Object photo = CreateObject("roAssociativeArray") photo.http=http photo.xml=xml photo.owner=owner photo.GetTitle=function():return m.xml@title:end function photo.GetID=function():return m.xml@id:end function photo.GetOwner=pGetOwner photo.GetURL=pGetURL Return photo End Function Function pGetOwner() as String If m.owner <> Invalid Return m.owner Return m.xml@owner End Function Function pGetURL() as String a=m.xml.GetAttributes() url="http://farm"a.farm".static.flickr.com/"a.server"/"a.id"_"a.secret".jpg" Return url End Function
Garbage Collection
BrightScript will automatically free strings when they are no longer used, and it will free objects when their reference count goes to zero. This is done at the time the object or string is no longer used; there is no background garbage collection task. This results in very predictable "garbage collection" – there are no unexpected stalls in execution.
A "mark and sweep" garbage collection is run after a script executes, or can be manually forced to run via the debug console. A script can force the Garbage Collector to run via the RunGarbageCollector() function. The Garbage Collector's purpose is to clean up objects that refer to themselves or have other circular references (which are not managed by the normal reference counting garbage collection).
i=roCreateObject("roInt") j=i ' reference incremented i=invalid ' reference decremented j=0 ' roInt just free'd.
Events
Events in BrightScript center around an event loop and the roMessagePort BrightScript Component. Any BrightScript Component can be posted to a message port. Typically these will be Objects that are designed to represent an events. For example, the roFilesystem class posts events of type roFilesystemEvent.
fs = CreateObject("roFilesystem") port = CreateObject("roMessagePort") fs.SetMessagePort(port) while true msg = wait(0, port) if type(msg)="roFileSystemEvent" then if msg.isStorageDeviceAdded() then print "device added" end if End While
Threading Model
A BrightScript script runs in a single thread. Multiple threads cannot be created in a BrightScript program. The general rule of thumb is that BrightScript Component calls are synchronous if they return quickly, or asynchronous if they take a long time to complete. For example, class roArray methods are all synchronous. But if "roVideoPlayer" is used to play a video, the Play() method returns immediately while video continues to play (it is asynchronous). As the video plays, it will post events to a message port. Typical events would be "media finished" or "time x reached". Some components have a choice of synchronous or asynchronous methods; for example roUrlTransfer has methods GetToString() which is synchronous (returns only after the transfer is complete) and AsyncGetToString() which is asynchronous (returns immediately and the transfer continues in the background).
This threading model means that the script writer does not have to deal with mutexes, condition variables, and other synchronization objects. The script merely receives and processes messages to handle events happening in the background.
Scope
BrightScript uses the following scoping rules:
- BrightScript does not support global variables. Except, there is one hard-coded global variable "global" that is an interface to the global BrightScript Component. The global component contains all global library functions. There is also a global context that can be accessed via the GetGlobalAA(). If in function scope and that function is not a method in an object, "m." also references the global associative array accessed with GetGlobalAA().
- Functions declared with the FUNCTION statement are at global scope, unless they are anonymous, in which case they are local scope.
- Local variables exist with function Scope. If a function calls another function, that new function has its own scope.
- Labels exist in function scope.
- Block Statements (like FOR-END FOR or WHILE-END WHILE) do not create a separate scope.
Creating and Using Intrinsic Objects
In most of this manual we use the term "object" to refer to a "BrightScript Component". These are C or C++ components with interfaces and member functions that BrightScript uses directly. Other than a few core objects that BrightScript relies upon (roArray, roAssociativeArray, roInt, etc.) BrightScript Components are platform specific.
You can also create "intrinsic" objects in BrightScript itself to use in your scripts. These act in some ways like Components; for example, the syntax to invoke a method in a Component or in an intrinsic object is identical: "object.function()". However, to be clear, these are not BrightScript Components. There is currently no way to create a BrightScript Component in BrightScript, or to create intrinsic objects that have interfaces (they only contain member functions, properties, or other objects).
An intrinsic object is simply an roAssociativeArray which contains function references. When a function is called "from" an Associative Array, a special pointer named "m" is accessible inside the function, which refers to the Associative Array. A "constructor" in BrightScript is a normal function at global scope that creates the Associative Array and fills in its member functions and properties.
"m" The BrightScript "this pointer"
A BrightScript object is an roAssociativeArray which contains function pointers. When a member function is called "from" an AssociativeArray, the special variable "m" is set to point to that AssociativeArray. "m" is accessible inside the called function to access other data in the AssociativeArray object.
function Main() obj = ConstructMyObject() obj.Set("hi!") print obj.Get() print "--------" print obj stop end function function ConstructMyObject() obj = { Set : function(x) : m.Value = x : end function Get : function() : return m.Value : end function Value : 0 } return obj end function
Output:
hi!
--------
value: hi!
get: <bsTypedValue: Function>
set: <bsTypedValue: Function>
Script Libraries
In addition to the platform BrightScript components discussed in Brief Summary of BrightScript Components, BrightScript enables platform BrightScript libraries to be used in your scripts.
BrightScript libraries are .brs files that are provided by the platform and compiled into your application when directed via the "Library" keyword to make additional functions available.
The firmware provides some common libraries under the system library directory "common:/LibCore".
Additional libraries may be provided by the platform for specific usage purposes.
Example
Library "v30/bslCore.brs"
The common library file sources can be viewed from the debug console:
BrightScript> bslCore = ReadAsciiFile("common:/LibCore/v30/bslCore.brs")
BrightScript> print bslCore
v30/bslCore.brs
This library provides platform constant definitions as well as some common utility functions.
bslBrightScriptErrorCodes() as Object
- Returns an roAssociativeArray with name value pairs of the error name and corresponding integer value, for example ERR_OKAY = &hFF.
bslGeneralConstants() as Object
- Returns an roAssociativeArray with name value pairs of system constants, for example MAX_INT = 2147483647.
bslUniversalControlEventCodes() as Object
- Returns an roAssociativeArray with name value pairs of the remote key code (buttons) constants, for example BUTTON_SELECT_PRESSED = 6.
AsciiToHex(ascii as String) as String
- Returns the hex encoded string, for example AsciiToHex("Hi!") = "486921".
HexToAscii(hex as String) as String
- Returns a string that is the hex decoded string, for example HexToAscii("486921") = "Hi!".
HexToInteger(hex as String) as Integer
- Returns the integer value of the passed in hex string.
v30/bslDefender.brs
This library includes 2D graphics helper functions on top of the native components.
Object dfNewBitmapSet(String filename)
The goal is to enable simple xml descriptions of graphics resources like bitmaps, regions, sprites, animations, and layouts to be used in your games. The library handles parsing, loading, rendering, and animation of sprites sheets (multiple images in a single png file).
- filename is the path to an XML file that contains info about bitmap regions, animation frames, and ExtraInfo metadata (any fields you would like) about resources used in 2d games.
- Returns an roAssociativeArray with the following name value pairs:
- ExtraInfo :roAssociativeArray
- Contains any name value pairs the designer desires.
- Regions : roAssociativeArray of name, roRegions pairs
- The roAssociative array returns a set of Regions from Region tag information from the xml.
- Used to describe regions of a bitmap
- Bitmap contains parameters: name, filename where filename is a .png, .gif, or .jpg file
- Region contains parameters: name, x, y, w, h and is a subtag of a bitmap
- Animations : roAssociativeArray of name, roArray of roRegion pairs.
- The top level roAssociativeArray is the animation name with a value of an array of frames that represent the animation
- Each frame is an roRegion that is represented in the xml by the "use" parameter. The use parameter refers to a previously defined Bitmap.Region returned in the Regions info.
- Animation frame descriptions can be used by the roSprite and roCompositor components.
- Backgrounds : roAssociativeArray of name, path pairs.
- Backgrounds do not create bitmaps at this point
- Use in conjunction with dfSetBackground() to manage backgrounds
- ExtraInfo :roAssociativeArray
dfDrawMessage(dest as Object, region as Object) as Void
- dest is an roScreen/roBitmap/roRegion and region is an roRegion
- greys the entire dest region and draws it the region centered on the drawable dest.
dfDrawImage(dest as Object, path as String, x as Integer, y as Integer) as Boolean
- Returns True if successful
- Creates a bitmap out of the image stored in the filename "path" and draws it at position (x,y) of the drawable dest.
dfSetupDisplayRegions(screen as Object, topx as Integer, topy as Integer, width as Integer, height as Integer) as Object
- Helper function to setup screen scaling with supplied pillar box or letterbox images to fill the entire screen.
- screen is an roScreen
- topx and topy are the coordinates of the upper left hand corner of the main drawing region
- Width and height is the size of the main drawing region
- Returns an associative array containing the following roRegions
Main: main drawable region
Left: left region if there is pillar box area on the left
Right: right region if there is a pillar box area on the right
Upper: upper region if there is a letterbox area at thetop
Lower: lower region if there is a letterbox area at the bottom
When using these regions as drawables, your graphics will be translated and clipped to these regions.
Object dfSetBackground(String backgroundName, Object backgrounds)
- dfSetBackground helps manage the limited video memory. The video memory does not currently run a defragmenter, and is very limited. These constraints make it important that large bitmaps (like backgrounds that fill the entire screen) are only allocated when needed. It is also helpful if you set your first initial background very early in your program, and then immediately replace the background after it is no longer in use. This helper function supports this background management for your application.
- backgroundName is a key for the Backgrounds roAssociative array of backgrounds.
- Backgrounds is an roAssociative array of background name keys and file path string values
- This function creates an roBitmap out of the background image file and returns a region the size of the entire roBitmap.
Example: bslDefender.brs
If spriteMap.xml contains the following:
<DefenderBitmapSet> <ExtraInfo cellsize="40"/> <Bitmap name="Background" filespec="pkg:/images/background.png" /> <Bitmap name="game-over" filespec="pkg:/images/gameover.png" /> <Bitmap name="title-screen" filespec="pkg:/images/Splash.gif" /> <Bitmap name="water_strip" filespec="pkg:/images/water_sprite.png"> <Region name="a" x="0" y="" w="40" h="40" t="225" /> <Region name="b" x="40" y="0" w="40" h="40" t="225" /> </Bitmap> <Animation name="water"> <frame use="water_strip.a" /> <frame use="water_strip.b" /> </Animation> </DefenderBitmapSet>
Then
BrightScript> xml = ReadAsciiFile("pkg:/images/map.xml")
BrightScript> bitmapset = dfNewBitmapSet(xml)
BrightScript> cellwidth=app.bitmapset.extrainfo.cellsize.toint()
BrightScript> print bitmapset.regions
Background: <Component: roRegion>
game-over: <Component: roRegion>
title-screen: <Component: roRegion>
water_strip.a: <Component: roRegion>
water_strip.b: <Component: roRegion>
water_strip: <Component: roRegion>
BrightScript> print bitmapset.animations.water
<Component: roRegion>
<Component: roRegion>
BrightScript> dfDrawMessage(screen, bitmapset.regions["game-over"])
BrightScript> REM screen now shows gameover.png image centered on screen