Forum Discussion

EnTerr's avatar
EnTerr
Roku Guru
10 years ago

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

2 Replies

  • 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.
  • 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).