Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
EnTerr
Level 8

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
EnTerr
Level 8

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
Level 8

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