Roku Developer Program

Join our online forum to talk to Roku developers and fellow channel creators. Ask questions, share tips with the community, and find helpful resources.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
EnTerr
Roku Guru

BrightScript vs XML: Scenography

I have somewhat of a meta question and that is, is there any particular reason that "SDK 2.0" scenic graphs should be underwritten in XML?
I understand that the tree (that a "scene graph" is) has to be represented somehow - but why not do that in BrightScript proper and instead resort to XML?

Here is what i mean. Let's take an example done in XML (i compacted it from the original, so it's easily viewable in the [code/] box):
<component name = "MaskGroupexample" extends = "Group" >
   <script>
       <![CDATA[
       sub init()
           maskgroupanimation = m.top.findNode("MaskGroupAnimation")
           maskgroupanimation.control = "start"
       end sub
       ]]>
   </script>
   <children>
       <MaskGroup id="exampleMaskGroup" maskuri="pkg:/images/GradientLinear.png" masksize="[512, 288]" maskOffset="[0, 0]" >
           <Poster uri = "pkg:/images/dialogpg.jpg" width = "512" height = "288" />
       </MaskGroup>
       <Animation id = "MaskGroupAnimation" duration = "3" easeFunction = "linear" repeat = "true" >
           <Vector2DFieldInterpolator id = "interpolator"  fieldToInterp = "exampleMaskGroup.maskOffset" key = "[ 0.0, 0.50, 1.0 ]"
               keyValue = "[ [512,0], [-512,0], [512,0] ]" />
       </Animation>
   </children>
</component>

The same can be written as B/S structures, in a format i invented - i argue it is no less readable this way:

["component", {name: "MaskGroupexample", extends: "Group"
   init: function()
       maskgroupanimation = m.top.findNode("MaskGroupAnimation")
       maskgroupanimation.control = "start"
   end function
   }
   ["children"
       ["MaskGroup", {id: "exampleMaskGroup", maskuri: "pkg:/images/GradientLinear.png",
                      masksize: [512, 288], maskOffset: [0, 0]}
           "Poster", {uri: "pkg:/images/dialogpg.jpg", width: 512, height: 288}
       ]
       ["Animation", {id: "MaskGroupAnimation", duration: 3, easeFunction: "linear", repeat: true}
           "Vector2DFieldInterpolator", {id: "interpolator", fieldToInterp: "exampleMaskGroup.maskOffset",
               key: [0.0, 0.50, 1.0], keyValue: [[512,0], [-512,0], [512,0]] }
       ]
   ]
]
... and this is perfectly valid BrightScript - just nested array and dictionary literals! It may look unusual if you don't know B/S implies comma when new line separates items but yeah... can save on commas there. And since this is all evaluated as B/S, attributes don't have to be string constants like "[ [512,0], [-512,0], [512,0] ]" - they are genuine expressions that can be calculated during object construction.

So back to my question - am i missing something?
Is there a hole in my B/S representation idea?
Am i overestimating how much people hate "coding" in XML?

I am addressing this both to Roku* people, as well as "outsiders" to the Co. that have already done some coding in SDK2.0
0 Kudos
2 REPLIES 2
EnTerr
Roku Guru

Re: BrightScript vs XML: Scenography

When i wrote the above SG "format" in B/S, i was trying to stay as close to XML as possible. So while better than XML, it's not exactly how a developer is inclined to write structures. But in my defense that format has a 1:1 mapping to XML, is non-ambiguous and i was planning of writing converter utility function, to build SG tree from it. Alas, the title "BrightScript/XML Markup Equivalence" i have seen turned out to be a ruse - there is no way (yet) to dynamically declare a new component type or add/change functions to it.

Now, if Roku's SG were properly supported by BrightScript, i should be able to do something like this instead (look ma, no xml!):
mg_ex = createObject("roSgNode", "Group")

mg_ex.createChild("MaskGroup", {id: "exampleMaskGroup", 
    maskuri: "pkg:/images/GradientLinear.png", masksize: [512, 288], maskOffset: [0, 0]
    children: [ 
      createObject("roSgNode", "Poster", {uri: "pkg:/images/dialogpg.jpg", width: 512, height: 288}) 
    ]
})

mga = createObject("roSgNode", "Animation", {id: "MaskGroupAnimation", 
    duration: 3, easeFunction: "linear", repeat: true
    children: [ 
      createObject("roSgNode", "Vector2DFieldInterpolator", {id: "interpolator", 
        fieldToInterp: "exampleMaskGroup.maskOffset", 
        key: [0.0, 0.50, 1.0], keyValue: [[512,0], [-512,0], [512,0]] 
      }) 
    ]
})
mg_ex.appendChild(mga)
mga.control = "start"
Much better but still tad tedious, since there is createObject() within createObject() or the use temp variables. But notice how "the need" to sub-class and write an initializer vanished naturally.

We can do better too, describe the object like so:
MaskGroupExample = { type: "Group"

 init: function()
     maskgroupanimation = m.top.findNode("MaskGroupAnimation")
     maskgroupanimation.control = "start"
 end function
 
 children: [
   { type: "MaskGroup", id: "exampleMaskGroup",
     maskuri: "pkg:/images/GradientLinear.png", masksize: [512, 288], maskOffset: [0, 0]
     children: [
       { type: "Poster", uri: "pkg:/images/dialogpg.jpg", width: 512, height: 288}
     ]
   }
   
   { type: "Animation", id: "MaskGroupAnimation",
     duration: 3, easeFunction: "linear", repeat: true
     children: [
       { type: "Vector2DFieldInterpolator", id: "interpolator",
         fieldToInterp: "exampleMaskGroup.maskOffset",
         key: [0.0, 0.50, 1.0], keyValue: [[512,0], [-512,0], [512,0]]
       }
     ]
   }
 ]
}
So that's ust a roAA, what do we do with it? Well, in a perfect world i should be able to create the node by calling
mg_ex = createObject("roSgNode", MaskGroupExample)
i.e. passing roAA as argument (instead of string) would be walked by the sgNode constructor, creating all nested nodes. In the mean time though - till the cows come home - it's trivial for us to write own B/S utility function that does the same by walking the tree and createObject() the nested levels.
0 Kudos
EnTerr
Roku Guru

Re: BrightScript vs XML: Scenography

So i have been dabbling in making RSG palatable to humans and here is what i mixed up, still a work in progress. This is a re-make of the
EventObserverExample and there is no XML and no subclassing (i.e. no need to create a custom <component/> just to do a trivial job!). All the custom code is BrightScript and main.brs goes like this:
sub main():
 screen = createObject("roSGScreen")
 port = createObject("roMessagePort")
 screen.setMessagePort(port)
 scene = screen.createScene("_scene")
 screen.show()
 scene.setFocus(true)
 scene.backgroundURI = "pkg:/images/rsgde_bg_hd.jpg"

 scene._ingest = {__: [
   { _: "Rectangle", id: "textRectangle"
     width: 640, height: 60
     color: "0x10101000"
     backgroundURI: "pkg:/images/rsgde_bg_hd.jpg"
     __: { _: "Label", id: "movingLabel"
           width: 280, height: 60
           horizAlign: "center", vertAlign: "center"
           text: "All The Best Videos!"
         }
   }
   { _: "Animation", id: "scrollbackAnimation"
     duration: 10
     repeat: true
     easeFunction: "linear"
     __: { _: "Vector2DFieldInterpolator"
           fieldToInterp: "movingLabel.translation"
           key: [0.0, 0.5, 1.0]
           keyValue: [[0.0, 0.0], [360.0, 0.0], [0.0, 0.0]]
         }
   }
   { _: "Timer", id: "textTimer"
     duration: 5
     repeat: true
   }
 ]}

 scene._ingest = {_m: {
   defaulttext: "All The Best Videos!"
   alternatetext: "All The Time!!!"
 }}

 scene._ingest = {_exec: [
"    _observeField(m.texttimer, ""fire"", function(msg):  "
"      lbl = m.movingLabel                                "
"      if lbl.text = m.defaultText:                       "
"        lbl.text = m.alternateText                       "
"      else:                                              "
"        lbl.text = m.defaultText                         "
"      end if                                             "
"    end function)                                        "
 ]}

 rect = scene.textRectangle.boundingRect()
 centerx = (1280 - rect.width) / 2
 centery = (720 - rect.height) / 2
 scene.textRectangle.translation = [centerx, centery]
 scene.textTimer.control = "start"
 scene.scrollbackAnimation.control = "start"

 while true:
   msg = wait(0, port)
   if type(msg) = "roSGScreenEvent" and msg.isScreenClosed() then exit while
 end while

end sub

Yes, this  code works as shown - not a hypothetical construct anymore. The "secret sauce" is in the _scene component, a thin veneer (couple of screens) that extends Scene with _ingest "executor" (a field with observer behind the scene, duh). The (ab)use of underscores is to avoid name clashes with upstream field names. Specifically "_" (roSgNode subtype) could have been named "_type" and "__" (child nodes) be "_children" - but i am a lazy typist and go as short as i can. 

Note there are no turducken issues in specifying the field values in B/S and no confusion! I.e. if one needs an array of strings, one would not end up with
<MyComp messages="[&quot;hi&quot;, &quot;hello&quot;, &quot;world&quot;, &quot;none&quot;]">
<!-- after failing with -->
<MyComp messages="['hi', 'hello', 'world', 'none']"

Note also that nodes with IDs currently get "auto-published" both as scene fields and in `m` - no need of findNode()ry. I am a bit ambivalent about choosing only one of the two - the reason is that the fields convenient to manipulate from main but glacial slow - where `m` variables are fast but encapsulated to the scenic scripts only.

Here is for comparison what the original example looks like (only the XML here):
<?xml version="1.0" encoding="utf-8" ?>
<!--********** Copyright 2016 Roku Corp.  All Rights Reserved. **********-->
<component name = "EventObserverExample" extends = "Scene" >

 <script type = "text/brightscript" >

   <![CDATA[

   sub init()
     m.top.backgroundURI = "pkg:/images/rsgde_bg_hd.jpg"

     example = m.top.findNode("textRectangle")
     examplerect = example.boundingRect()
     centerx = (1280 - examplerect.width) / 2
     centery = (720 - examplerect.height) / 2
     example.translation = [ centerx, centery ]

     m.movinglabel = m.top.findNode("movingLabel")
     m.defaulttext = "All The Best Videos!"
     m.alternatetext = "All The Time!!!"
     m.textchange = false

     scrollback = m.top.findNode("scrollbackAnimation")
     scrollback.control = "start"

     texttimer = m.top.findNode("textTimer")
     texttimer.observeField("fire", "changetext")
     texttimer.control = "start"

     m.top.setFocus(true)
   end sub

   sub changetext()
     if (m.textchange = false) then
       m.movinglabel.text = m.alternatetext
       m.textchange = true
     else
       m.movinglabel.text = m.defaulttext
       m.textchange = false
     end if
   end sub

   ]]>

 </script>

 <children>

   <Rectangle
     id = "textRectangle"
     width = "640"
     height = "60"
     color = "0x10101000" >
     <Label
       id = "movingLabel"
       width = "280"
       height = "60"
       text = "All The Best Videos!"
       horizAlign = "center"
       vertAlign = "center" />

   </Rectangle>

   <Animation
     id = "scrollbackAnimation"
     duration = "10"
     repeat = "true"
     easeFunction = "linear" >

     <Vector2DFieldInterpolator
       key = "[ 0.0, 0.5, 1.0 ]"
       keyValue = "[ [0.0, 0.0], [360.0, 0.0], [0.0, 0.0] ]"
       fieldToInterp = "movingLabel.translation" />

   </Animation>
   <Timer
     id = "textTimer"
     repeat = "true"
     duration = "5" />

 </children>

</component>

I'd like to hear some opinions from people that have tried RSG - beginners or advanced. Do i make any sense in what i am trying to do? Does it seem more simple or more complicated than the Co's beloved "xmlography"? Also the m v. fields question. 

My biggest issue remains the lack of first-class function fields in RSG, thus having to pass code as strings to the scene. Also, i haven't touched the roSgScreen main loop - no opinion as of yet if it's helpful to tuck it away (i have the feeling i'll get opinionated when i get to use roMessageEvent observers).
0 Kudos