Skip to content

Latest commit

 

History

History
102 lines (95 loc) · 18.5 KB

limitations.md

File metadata and controls

102 lines (95 loc) · 18.5 KB

BrightScript Engine Current Limitations

The BrightScript Engine implements the BrightScript language specification up to Roku OS 16.0. However, some features and components remain unsupported or only partially implemented. These limitations fall into three categories: features planned for future development, components that will remain as mock objects for compatibility purposes, and functionality considered outside the project's scope. The following sections detail each category and its current status.

In Scope (to be developed/fixed in future releases)

  • Roku SceneGraph framework support is implemented as an extension published separately as the brs-scenegraph NPM package. All SceneGraph nodes documented by Roku are available, however because the engine is a simulator and not a hardware emulator, expect rendering differences from a real Roku device, please open a GitHub issue if you find any problem or missing feature. These are the current known limitations:
    • The Task node is implemented but its behavior is limited:
      • For now only 30 concurrent task threads are supported per application
      • Only one port instance can be used on Task init() to observe fields
      • Rendezvous is implemented, but may not work properly for all edge cases.
      • Task nodes never spawn under the synchronous executeFile() execution model of the Node.js library — use executeApp() for Task support, see the Node.js library guide.
    • The focus change native animation on grids, lists and panel nodes is not implemented yet.
    • Some abstract base nodes are not implemented on their own and fall back to a plain Group (like the Standard Dialog framework's StdDlgAreaBase, StdDlgItemGroup and StdDlgGraphicItem).
    • Component Libraries are supported, both declared in XML (<ComponentLibrary id="..." uri="..."/>) and created at runtime via CreateObject("roSGNode", "ComponentLibrary") with the uri assigned in code. The component namespace is the library manifest's sg_component_libs_provided value (falling back to the node id when not declared), so its components are referenced as LibraryName:ComponentName. The uri may point to a local volume (pkg:/, tmp:/, ext1:/) or a remote http(s):// package (the configured CORS proxy is honored). Current limitations:
      • Libraries are fetched and compiled synchronously (the BrightScript event loop never yields, so an asynchronous load could not complete mid-execution). The node reports loadStatus = "loading" immediately and the "ready"/"failed" transition is emitted on the next render frame, so observeField("loadStatus", ...) callbacks are notified as on a real device — provided the app is pumping its screen message loop (wait(...)).
      • Inside a library, pkg:/ still resolves to the host app's package (not the library's own); reference a library's bundled scripts with relative uri paths in its component XML.
      • Library source/ global functions and intra-library component inheritance (a library component extending another library component) are not supported yet.
      • Encrypted/signed libraries are not supported (plain .zip only).
  • Node identity, cloning and render-thread-only APIs differ from a device in three measured cases (probes: test/simulator/probes/node-container-copy-probe, clone-and-setref-probe, clone-basetype-probe):
    • roUtils.IsSameObject() compares the engine's node instance, so it returns true for two values that refer to the same node. A device mints a fresh BrightScript handle on every node retrieval (a node field read, getField, findNode, getChild, getParent, even a node returned by roUtils.DeepCopy), so it answers false in almost every real case. Use isSameNode() for node identity — it matches a device. Copy behavior itself is correct: reading an assocarray/array field, roUtils.DeepCopy and moveIntoField all carry the node over unchanged, and clone shares node-valued fields; only the identity report differs.
    • clone() never re-runs a component's init(), so the clone's script scope is empty and a callFunc on it reads invalid for anything the component cached in m — this matches a device. Note the device quirk the engine now reproduces: a custom component whose root built-in base is Node clones to a bare Node (subtype, declared and addField fields, and functions all dropped, leaving only Node's four fields with their current values); rooted at any other built-in — Group, Label, ContentNode — the clone keeps its subtype, fields and functions (a component extending another custom component follows its root).
    • The engine runs source/ code and the SceneGraph tree on one thread, so it has no main-vs-render ScriptEngine split. APIs a device restricts to the render thread are therefore accepted from Main — notably setRef(), getRef() and canGetRef(), which a device fails (returning false, leaving the field unassigned) when called from the main thread. Their render-thread semantics match a device exactly.
  • The complete Roku OS file system is available and shared among threads.
    • Supports all volumes: pkg:, common:, tmp:, cachefs: and ext1:.
    • By default, the external volume (ext1:) and the writeable volumes (tmp: cachefs:) are limited to 32 MB each.
    • The writable volumes tmp: and cachefs: limits are configurable, see customization documentation;
    • The ext1: limit is fixed.
    • As on a device, tmp: is emptied on every app launch, while cachefs: survives app relaunches for the session (browser page or Node.js process) — reloading the page or exiting the process acts as a device reboot and clears it. Unlike a device, cachefs: is a single volume shared by all apps, not segregated by developer ID. The CLI (brs-cli) runs one app per process, so it always starts with an empty cachefs:. When an app launches with cachefs: over 75% full, the oldest-modified files are evicted until it is back to half, as a device frees cache space. Eviction only happens at launch: while an app runs, a write that doesn't fit fails (and leaves no file behind), and space freed by deleting or rewriting files is only reclaimed on the next launch. A file being written when the app is terminated may be left incomplete.
  • Audio playback is implemented via roAudioResources, roAudioPlayer components and SoundEffect, Audio nodes, but with some limitations:
    • Only one instance of roAudioPlayer component or Audio node should be used, if more are created those will share the content playlist.
    • If the roAudioPlayer component or Audio node instance is destroyed the audio keeps playing, make sure to stop the playback before discarding the object.
    • No Timed Metadata support.
  • Video playback is implemented via roVideoPlayer component and Video node, but with some limitations:
    • If the instance of roVideoPlayer or Video is destroyed the video keeps playing, make sure to stop before discarding the object.
    • The following roVideoPlayer methods are not supported, implemented as mock: setCGMS, setMaxVideoDecodeResolution, setTimedMetadataForKeys, getCaptionRenderer
    • Check what formats (container and codec) can be used on each browser, using roDeviceInfo.canDecodeVideo(), to make sure your video can be played.
    • Under Node.js/CLI there is no audio or video output: autoPlayEnabled is forced off and media events are only simulated, so apps behave as if playback never starts.
    • Subtitles are only supported for HLS and DASH streams and only in Video node, the roVideoPlayer does not support subtitles until roCaptionRenderer is implemented.
    • BIF (Base Index Frames) thumbnails are not yet supported.
  • The component roUrlTransfer is implemented with basic functionality but with the following limitations:
    • To make a web app access urls from domains other than the one it is hosted, the Cross-Origin Resource Sharing (CORS) browser policy requires the server called to respond with the header Access-Control-Allow-Origin, read more.
      • A simple way to overcome this limitation is to use a CORS proxy like the cors-anywhere, see customization documentation to learn how to configure brs-engine to use it.
      • If you are using a Chromium based browser (Chrome, Edge, Brave, etc) you can install this extension to bypass CORS.
    • The async methods (AsyncGetToString(), AsyncGetToFile(), AsyncHead(), AsyncPostFromString(), AsyncPostFromFile(), AsyncPostFromFileToFile()) run on a background worker thread while the app keeps running, and their roUrlEvent arrives on the message port when the transfer completes, as on a device. They fall back to running synchronously (on the next Wait()/GetMessage()) in three cases:
      • The background worker is unavailable: on the browser package, the page is not cross-origin isolated (no SharedArrayBuffer); on the Node.js library, the app runs with the synchronous executeFile() instead of executeApp().
      • The background worker stops responding for 15 seconds, for example while busy decoding a huge image. New transfers go back to it once it responds again.
      • The transfer would fail before reaching the network: an https URL with a missing certificates file, or a missing body file for AsyncPostFromFileToFile(). The synchronous path reports these errors.
    • Custom/Self-Signed SSL certificates are not supported, the engine will use default browser client certificate database.
    • As custom certificates are not supported these methods are just mocked and do nothing: EnablePeerVerification, EnableHostVerification, SetCertificatesDepth.
    • Cookies are only partially supported, if EnableCookies is called and EnableFreshConnection is set to false, then Cookies from previous calls will be preserved.
    • The other Cookies related methods are just mocked and do nothing: GetCookies, AddCookies, ClearCookies.
    • The following methods are also only mocked but do nothing: EnableResume, SetHttpVersion and SetMinimumTransferRate.
    • Server-Sent Events (SSE) (Roku OS 16.0) are supported via AsyncGetSSEvents(), delivering real streaming roSSEvents on both packages: on Node.js/CLI the request runs in a small per-transfer helper process, and on the browser package the fetch() runs on the main thread, bridged to the worker via shared memory (so the CORS limitation above applies too). On the browser package, request headers that browsers forbid script to set (see AddHeader()) are silently dropped.
  • The component roTextureManager decodes and downloads asynchronous texture requests on a background worker thread, so the app keeps running while images load, as on a device. Requests for the same URI share one load. Limitations:
    • Like asynchronous roUrlTransfer transfers, requests load synchronously (when the message port is read) when the background worker is unavailable or not responding (see above). Requests made before SetMessagePort() also load synchronously.
    • Requests with SetAsync(false) load synchronously, as documented.
    • Delivering a loaded image still copies its pixels on the BrightScript thread, about 2 ms for a 2048×1024 image.
    • The SceneGraph Poster node and other nodes that load images from a uri field still download and decode them synchronously, stalling the frame that loads them.
  • The roStreamSocket (TCP) component performs real network I/O, on the Node.js/CLI package only.
    • Supports listening, connecting, accepting connections, and sending/receiving stream data, including roSocketEvent delivery via NotifyReadable()/Wait(), backed by a small helper process per listener/connection.
    • Connect() uses a blocking request with an 8-second timeout rather than modeling Roku's async-per-message-port connect semantics.
    • SetLinger/SetMaxSeg (ifSocketConnectionOption) are accepted and stored but not enforced — Node's net module exposes no public SO_LINGER/TCP_MAXSEG hook.
    • On the browser package roStreamSocket remains mocked, as browsers cannot open raw sockets.
  • The roDataGramSocket (UDP) component performs real network I/O, on the Node.js/CLI package only.
    • Supports binding, broadcast, send and receive, including roSocketEvent delivery via NotifyReadable()/Wait(), backed by a small per-socket helper process.
    • Real multicast group membership (JoinGroup/DropGroup) is not implemented on either platform.
    • On the browser package roDataGramSocket remains mocked, as browsers cannot open raw sockets.
  • The new roWebSocket component (Roku OS 16.0) performs real WebSocket network I/O on both packages, unlike the raw-socket components above:
    • On the Node.js/CLI package, a per-socket helper process (backed by the ws package) gives full protocol fidelity, including real Ping/Pong control frames (SendPing/SendPong/PingTest/SetAutoPingReply), custom handshake headers, HTTP Basic-Auth credentials (SetUserAndPassword), and TLS peer/host verification toggles.
    • On the browser package, the real WebSocket connection is opened on the main thread (the interpreter's own thread cannot service its callbacks) and bridged to the worker via shared memory. Browsers give script no way to set handshake headers or credentials and no way to observe or originate Ping/Pong control frames, so on that package: SetUserAndPassword, custom ifHttpAgent headers, and EnablePeerVerification/EnableHostVerification are accepted but have no effect; SendPing/SendPong/PingTest are no-ops (the browser still auto-replies to an incoming Ping, invisibly to script, regardless of SetAutoPingReply).
    • GetBuffered() always returns 0 and SetFragmentSize()/GetMsgSendBufferSize()/GetMsgRecvBufferSize() are stored but not enforced — neither platform's transport models outgoing buffering or manual frame fragmentation.
  • The new roAnimatedImage (BrightScript component) and AnimatedImage (SceneGraph node), added by Roku OS 15.3, now follow Roku's published specification (field names, defaults, and the control/state vocabulary).
    • Animated WebP frames are decoded and composited (including dispose/blend semantics) directly by the engine.
    • Lottie/Bodymovin JSON is rendered via the third-party lottie.js library (pre-1.0, "covers a growing subset of the format" per its own documentation) — not every Lottie feature (precomps, mattes, masks, expressions) is confirmed supported.
    • video/mp4 is a documented mimeType, but MP4-encoded animated content is not yet decoded — content that sniffs or declares as MP4 fails to load.
    • loadingBitmapUri/failedBitmapUri (placeholder image swap while loading/on failure) are accepted fields but not yet functional.
  • The roInput deep link events are supported, but the events related to Voice Commands are not available yet.
  • The component roAudioMetadata only supports MP3 (for now).
  • The component roImageMetadata only supports JPEG images (for now).
  • The component roAppMemoryMonitor will only return measured data in Node.JS and Chromium browsers. For browsers the memory heap info only accounts for the main thread, as WebWorkers do not have support for performance.memory API. The roAppMemoryMonitorEvent is not yet implemented.
  • roRegex patterns are compiled by the JavaScript RegExp engine, not PCRE. Common PCRE-only syntax is translated: inline options ((?i), (?-i), (?i:...), (?s), (?m), (?x)), possessive quantifiers, atomic groups, (?#...) comments, (?P<n>...)/(?P=n), \A/\z/\Z, POSIX classes, \p{..}, \Q...\E and \x{...}. Other PCRE-only syntax (recursion, conditionals, \K, \G, backtracking verbs) makes CreateObject("roRegex", ...) print roRegex: Invalid pattern and return invalid, where a device would accept the pattern. Case-insensitive matching of back references inside an (?i) scope is not emulated.
  • The global functions Eval(), GetLastRunCompileError() and GetLastRunRuntimeError() are not available.
  • The string mod cannot be used as variable or function parameter name, because it conflicts with remainder operator Mod (Roku devices allows that).
  • Screensaver functionality is not yet implemented.
  • SDK 1.0 deprecated components are not supported, but may be implemented in the future as a new extension, allowing legacy apps to be executed.

Mocked Components and Libraries

  • RAF (Roku Ads Framework) library that exposes Roku_Ads object is mocked with the most common methods available returning static values.
  • RED (Roku Event Dispatcher) and Google IMA3 libraries are also mocked.
  • Channel Store components (ChannelStore, roChannelStore and roChannelStoreEvent) are mocked with support for the fakeServer() feature: every documented command is implemented, with catalog, purchase, order and account data coming from the csfake/*.xml test files (or a canned account) only while fakeServer is enabled. Two exceptions work regardless of fakeServer:
    • The channel credential store (storeChannelCredData/getChannelCred) holds the app's own artifact in memory, though the data does not persist between runs or across devices.
    • The GetRokuCustomerId command (Roku OS 16.0), sent through the ChannelStore node's generic request framework (request/requestStatus fields), returns the customerId configured in the device information (see customization). The generic request framework only supports this command, and only on the node: Roku documents no roChannelStore method for it.
  • The Text to Speech components (roAudioGuide, roTextToSpeech and roMicrophone) and the Signing Algorithm components (roDSA and roRSA) are mocked: they expose their documented methods returning static values, but perform no real speech, recording or cryptographic signing.
  • Several components have their methods and events mocked, they return constant values to prevent crash. Those are mostly related to device behaviors that are not possible to replicate in a browser environment or simply not applicable to the engine.

Out of Scope

  • Roku OS User Interface.
  • Roku Channel Store features.
  • Video playback with Ads.