Skip to content

Commit

Permalink
Implement roCECStatus component (#430)
Browse files Browse the repository at this point in the history
* Implemented `roCECStatus`

* Updating the interface name of other event objects

* Updated limitations document

* Added test case for Events and fixed issues
  • Loading branch information
lvcabral authored Dec 31, 2024
1 parent 39976c1 commit 657bfa4
Show file tree
Hide file tree
Showing 18 changed files with 274 additions and 20 deletions.
29 changes: 18 additions & 11 deletions browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ appIcons.forEach((icon, index) => {
icon.onclick = () => loadZip(appList[index].id);
});

// App Configuration
// Pause the engine when the browser window loses focus
// If `false` use `roCECStatus` in BrightScript to control app behavior
const pauseOnBlur = false;
let debugMode = "continue";

// App status objects
let currentApp = { id: "", running: false };
let currentZip = null;
let debugMode = "continue";

// Start the engine
appInfo.innerHTML = "<br/>";
Expand Down Expand Up @@ -342,17 +347,19 @@ function toggleDiv(divId) {
}
}

window.onfocus = function () {
if (currentApp.running && debugMode === "pause") {
brs.debug("cont");
}
};
if (pauseOnBlur) {
window.onfocus = function () {
if (currentApp.running && debugMode === "pause") {
brs.debug("cont");
}
};

window.onblur = function () {
if (currentApp.running && debugMode === "continue") {
brs.debug("pause");
}
};
window.onblur = function () {
if (currentApp.running && debugMode === "continue") {
brs.debug("pause");
}
};
}

// Browser Event Handler
function openBrowser(url, width, height) {
Expand Down
3 changes: 1 addition & 2 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ There are several features from the **BrightScript** language and components tha

* RSG (Roku SceneGraph) SDK components are not currently supported.
* The following components are also not implemented yet:
* `roCECStatus`
* `roAudioGuide`
* `roDataGramSocket`
* `roDSA`
Expand All @@ -16,7 +15,6 @@ There are several features from the **BrightScript** language and components tha
* `roRSA`
* `roSocketAddress`
* `roStreamSocket`
* `roSystemLog`
* `roTextToSpeech`
* `roTextureManager`
* `roTextureRequest`
Expand Down Expand Up @@ -51,6 +49,7 @@ There are several features from the **BrightScript** language and components tha

* RAF (Roku Ads Framework) object `Roku_Ads` is mocked with the most common methods available.
* Channel Store components (`roChannelStore` and `roChannelStoreEvent`) are mocked (a fake server feature will be implemented in the future).
* 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

Expand Down
13 changes: 13 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,3 +689,16 @@ function apiException(level: string, message: string) {
console.warn(message);
}
}

// CEC Status Update
window.onfocus = function () {
if (currentApp.running) {
Atomics.store(sharedArray, DataType.CEC, 1);
}
};

window.onblur = function () {
if (currentApp.running) {
Atomics.store(sharedArray, DataType.CEC, 0);
}
};
2 changes: 2 additions & 0 deletions src/core/brsTypes/components/BrsObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { RoInvalid } from "./RoInvalid";
import { RoFunction } from "./RoFunction";
import { Callable } from "../Callable";
import { RoNDK } from "./RoNDK";
import { RoCECStatus } from "./RoCECStatus";

// Class to define a case-insensitive map of BrightScript objects.
class BrsObjectsMap {
Expand Down Expand Up @@ -194,4 +195,5 @@ export const BrsObjects = new BrsObjectsMap([
["roURLTransfer", (interpreter: Interpreter) => new RoURLTransfer(interpreter)],
["roInvalid", (_: Interpreter) => new RoInvalid(), -1],
["roNDK", (_: Interpreter) => new RoNDK()],
["roCECStatus", (interpreter: Interpreter) => new RoCECStatus(interpreter)],
]);
2 changes: 1 addition & 1 deletion src/core/brsTypes/components/RoAudioPlayerEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class RoAudioPlayerEvent extends BrsComponent implements BrsValue {
break;
}
this.registerMethods({
ifAudioPlayerEvent: [
ifroAudioPlayerEvent: [
this.getIndex,
this.getMessage,
this.getInfo,
Expand Down
87 changes: 87 additions & 0 deletions src/core/brsTypes/components/RoCECStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { BrsValue, ValueKind, BrsInvalid, BrsBoolean } from "../BrsType";
import { BrsComponent } from "./BrsComponent";
import { BrsEvent, BrsType, RoMessagePort } from "..";
import { Callable, StdlibArgument } from "../Callable";
import { Interpreter } from "../../interpreter";
import { RoCECStatusEvent } from "./RoCECStatusEvent";
import { DataType } from "../../common";

export class RoCECStatus extends BrsComponent implements BrsValue {
readonly kind = ValueKind.Object;
private readonly interpreter: Interpreter;
private port?: RoMessagePort;
private active: number;

constructor(interpreter: Interpreter) {
super("roCECStatus");
this.interpreter = interpreter;
this.active = 1; // Default to active
this.registerMethods({
ifCECStatus: [this.isActiveSource, this.getMessagePort, this.setMessagePort],
});
}

toString(parent?: BrsType): string {
return "<Component: roCECStatus>";
}

equalTo(other: BrsType) {
return BrsBoolean.False;
}

dispose() {
this.port?.unregisterCallback(this.getComponentName());
}

// System Log Event -------------------------------------------------------------------------------

private getNewEvents() {
const events: BrsEvent[] = [];
const cecActive = Atomics.load(this.interpreter.sharedArray, DataType.CEC);
if (cecActive >= 0 && cecActive !== this.active) {
this.active = cecActive;
events.push(new RoCECStatusEvent(this.active !== 0));
}
return events;
}

// ifCECStatus ---------------------------------------------------------------------------------

/** Indicates whether the device is the active source. */
private readonly isActiveSource = new Callable("isActiveSource", {
signature: {
args: [],
returns: ValueKind.Boolean,
},
impl: (_: Interpreter) => {
const cecActive = Atomics.load(this.interpreter.sharedArray, DataType.CEC);
return BrsBoolean.from(cecActive !== 0);
},
});

/** Returns the message port (if any) currently associated with the object */
private readonly getMessagePort = new Callable("getMessagePort", {
signature: {
args: [],
returns: ValueKind.Object,
},
impl: (_: Interpreter) => {
return this.port ?? BrsInvalid.Instance;
},
});

/** Sets the roMessagePort to be used for all events from the screen */
private readonly setMessagePort = new Callable("setMessagePort", {
signature: {
args: [new StdlibArgument("port", ValueKind.Dynamic)],
returns: ValueKind.Void,
},
impl: (_: Interpreter, port: RoMessagePort) => {
const component = port.getComponentName();
this.port?.unregisterCallback(component);
this.port = port;
this.port.registerCallback(component, this.getNewEvents.bind(this));
return BrsInvalid.Instance;
},
});
}
60 changes: 60 additions & 0 deletions src/core/brsTypes/components/RoCECStatusEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { BrsValue, ValueKind, BrsBoolean, BrsString } from "../BrsType";
import { BrsComponent } from "./BrsComponent";
import { BrsType, Int32, toAssociativeArray } from "..";
import { Callable } from "../Callable";
import { Interpreter } from "../../interpreter";

export class RoCECStatusEvent extends BrsComponent implements BrsValue {
readonly kind = ValueKind.Object;
private readonly active: boolean;

constructor(active: boolean) {
super("roCECStatusEvent");
this.active = active;
this.registerMethods({
ifroCECStatusEvent: [this.getInfo, this.getIndex, this.getMessage],
});
}

toString(parent?: BrsType): string {
return "<Component: roCECStatusEvent>";
}

equalTo(other: BrsType) {
return BrsBoolean.False;
}

/** The index value of this event is not used and is always set to 0. */
private readonly getIndex = new Callable("getIndex", {
signature: {
args: [],
returns: ValueKind.Int32,
},
impl: (_: Interpreter) => {
return new Int32(0);
},
});

/** Returns the string "CECStatus". */
private readonly getMessage = new Callable("getMessage", {
signature: {
args: [],
returns: ValueKind.String,
},
impl: (_: Interpreter) => {
return new BrsString("CECStatus");
},
});

/** Returns an roAssociativeArray with the current status of the device or the caption mode. */
private readonly getInfo = new Callable("getInfo", {
signature: {
args: [],
returns: ValueKind.Object,
},
impl: (_: Interpreter) => {
const state = this.active ? "ACTIVE" : "INACTIVE";
return toAssociativeArray({ Active: this.active, ActiveSourceState: state });
},
});
}
2 changes: 1 addition & 1 deletion src/core/brsTypes/components/RoDeviceInfoEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class RoDeviceInfoEvent extends BrsComponent implements BrsValue {
this.isStatusMsg = true;
}
this.registerMethods({
ifDeviceInfoEvent: [this.getInfo, this.isStatusMessage, this.isCaptionModeChanged],
ifroDeviceInfoEvent: [this.getInfo, this.isStatusMessage, this.isCaptionModeChanged],
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/brsTypes/components/RoSystemLogEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class RoSystemLogEvent extends BrsComponent implements BrsValue {
super("roSystemLogEvent");
this.data = data;
this.logType = logType;
this.registerMethods({ ifSystemLogEvent: [this.getInfo] });
this.registerMethods({ ifroSystemLogEvent: [this.getInfo] });
}

toString(parent?: BrsType): string {
Expand Down
2 changes: 1 addition & 1 deletion src/core/brsTypes/components/RoVideoPlayerEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class RoVideoPlayerEvent extends BrsComponent implements BrsValue {
break;
}
this.registerMethods({
ifVideoPlayerEvent: [
ifroVideoPlayerEvent: [
this.getIndex,
this.getMessage,
this.getInfo,
Expand Down
7 changes: 5 additions & 2 deletions src/core/brsTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { RoAudioPlayerEvent } from "./components/RoAudioPlayerEvent";
import { RoVideoPlayerEvent } from "./components/RoVideoPlayerEvent";
import { RoChannelStoreEvent } from "./components/RoChannelStoreEvent";
import { RoDeviceInfoEvent } from "./components/RoDeviceInfoEvent";
import { RoCECStatusEvent } from "./components/RoCECStatusEvent";

export * from "./BrsType";
export * from "./Int32";
Expand Down Expand Up @@ -227,7 +228,8 @@ export function isBrsEvent(value: BrsType): value is BrsEvent {
value instanceof RoAudioPlayerEvent ||
value instanceof RoVideoPlayerEvent ||
value instanceof RoChannelStoreEvent ||
value instanceof RoDeviceInfoEvent
value instanceof RoDeviceInfoEvent ||
value instanceof RoCECStatusEvent
);
}

Expand All @@ -240,7 +242,8 @@ export type BrsEvent =
| RoAudioPlayerEvent
| RoVideoPlayerEvent
| RoChannelStoreEvent
| RoDeviceInfoEvent;
| RoDeviceInfoEvent
| RoCECStatusEvent;

/**
* The set of all comparable BrightScript types. Only primitive (i.e. intrinsic * and unboxed)
Expand Down
1 change: 1 addition & 0 deletions src/core/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ export enum DataType {
MUHS, // Memory Used Heap Size
MHSL, // Memory Heap Size Limit
MBWD, // Measured Bandwidth
CEC, // Consumer Electronics Control
// Key Buffer starts here: KeyBufferSize * KeyArraySpots
RID, // Remote Id
KEY, // Key Code
Expand Down
4 changes: 3 additions & 1 deletion src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ declare global {
*/
const arrayLength = dataBufferIndex + dataBufferSize;
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * arrayLength);
shared.set("buffer", new Int32Array(sharedBuffer));
const sharedArray = new Int32Array(sharedBuffer);
sharedArray.fill(-1);
shared.set("buffer", sharedArray);

globalThis.postMessage = (message: any) => {
if (typeof message === "string") {
Expand Down
19 changes: 19 additions & 0 deletions test/e2e/BrsComponents.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -557,4 +557,23 @@ describe("end to end brightscript functions", () => {
"GamePad? false",
]);
});
test("components/roEvents.brs", async () => {
await execute([resourceFile("components", "roEvents.brs")], outputStreams);
expect(allArgs(outputStreams.stdout.write).map((arg) => arg.trimEnd())).toEqual([
"roCECStatus.isActiveSource() = true",
"roDeviceInfoEvent",
"roDeviceInfoEvent.isCaptionModeChanged = true",
"roDeviceInfoEvent.isStatusMessage = false",
`<Component: roAssociativeArray> =\n` +
"{\n" +
` Mode: "Off"\n` +
` Mute: false\n` +
`}`,
"<Interface: ifroDeviceInfoEvent>",
"roChannelStoreEvent",
`<Component: roArray> =\n` + `[\n` + `]`,
"<Interface: ifChannelStoreEvent>",
"Invalid",
]);
});
});
40 changes: 40 additions & 0 deletions test/e2e/resources/components/roEvents.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
sub main()
port = CreateObject("roMessagePort")
di = CreateObject("roDeviceInfo")
di.SetMessagePort(port)
cec = CreateObject("roCECStatus")
print "roCECStatus.isActiveSource() = "; cec.isActiveSource()
cec.SetMessagePort(port)
syslog = CreateObject("roSystemLog")
syslog.SetMessagePort(port)
syslog.EnableType("bandwidth.minute")
store = CreateObject("roChannelStore")
store.SetMessagePort(port)
store.GetCatalog()
for t = 1 to 10
msg = port.getMessage()
print type(msg)
if type(msg) = "roCECStatusEvent"
print msg.getMessage()
print msg.getIndex()
print msg.getInfo()
print FindMemberFunction(msg, "getInfo")
else if type(msg) = "roDeviceInfoEvent"
print "roDeviceInfoEvent.isCaptionModeChanged = "; msg.isCaptionModeChanged()
print "roDeviceInfoEvent.isStatusMessage = "; msg.isStatusMessage()
print msg.getInfo()
print FindMemberFunction(msg, "getInfo")
else if type(msg) = "roChannelStoreEvent"
print msg.getResponse()
print FindMemberFunction(msg, "getResponse")
else if type(msg) = "roSystemLogEvent"
logEvent = msg.getInfo()
if logEvent.logType = "bandwidth.minute"
print logEvent.logType, logEvent.dateTime.toISOString(), logEvent.bandwidth
end if
print FindMemberFunction(msg, "getInfo")
else msg = invalid
exit for
end if
end for
end sub
Loading

0 comments on commit 657bfa4

Please sign in to comment.