Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SceneGraph Node files reorganization #86

Merged
merged 8 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ Regardless of whether you're fixing bugs or implementing new features, there's a
1. Create a fork of this repo if you haven't already
1. Send us a [pull request](https://github.com/rokucommunity/brs/pulls)!

### Adding a component
### Adding a Node component

For guidelines on adding a component to `brs`, see [this doc](docs/AddingComponents.md).
For guidelines on adding a Node component to `brs`, see [this doc](docs/AddingComponents.md).

## What We Look For in a Pull Request

Expand Down
24 changes: 12 additions & 12 deletions docs/AddingComponents.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
## Guidelines for adding components to `brs`
## Guidelines for adding node components to `brs`

This is aimed to be a quick guide for adding a component to `brs`. Note that this may not be comprehensive in all cases, but is a general plan of attack. Here are the steps you should take:
This is aimed to be a quick guide for adding a node component to `brs`. Note that this may not be comprehensive in all cases, but is a general plan of attack. Here are the steps you should take:

1. Find the documentation for your component on [Roku's developer docs](https://developer.roku.com). For example, the documentation for the `Group` component can be found [here](https://developer.roku.com/en-gb/docs/references/scenegraph/layout-group-nodes/group.md).
1. Create a file in the [components directory](https://github.com/rokucommunity/brs/tree/master/src/brsTypes/components) called `<insert component name>.ts`.
1. Find the documentation for your component on [Roku's developer docs](https://developer.roku.com). For example, the documentation for the `Group` component can be found [here](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/group.md).
1. Create a file in the [nodes directory](https://github.com/rokucommunity/brs/tree/master/src/brsTypes/nodes) called `<insert component name>.ts`.
1. Copy the following code and paste it into your new file:

```
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { AAMember } from "../components/RoAssociativeArray";

export class <insert component name> extends <insert parent component> {
export class <insert node name> extends <insert parent node> {
readonly defaultFields: FieldModel[] = [
// Add built-in fields here.
// The fields can be found on the Roku docs.
];

constructor(initializedFields: AAMember[] = [], readonly name: string = "<insert component name>") {
constructor(initializedFields: AAMember[] = [], readonly name: string = "<insert node name>") {
super([], name);

this.registerDefaultFields(this.defaultFields);
Expand All @@ -25,10 +25,10 @@ This is aimed to be a quick guide for adding a component to `brs`. Note that thi
}
```

1. Replace all `<insert component name>` and `<insert parent component>` from above with your component name. Add any built-in fields and/or class functions that the Roku docs specify.
1. Add a constructor definition to the [component factory](https://github.com/rokucommunity/brs/blob/main/src/brsTypes/components/ComponentFactory.ts). This will allow instances of your new component to be created dynamically when it is encountered in XML or BrightScript code.
1. Add a test case for your Typescript class in [the components test directory](https://github.com/rokucommunity/brs/tree/master/test/brsTypes/components). Use the existing component test files in that directory as a model for what your test should look like.
1. Replace all `<insert node name>` and `<insert parent node>` from above with your node name. Add any built-in fields and/or class functions that the Roku docs specify.
1. Add a constructor definition to the [node factory](https://github.com/rokucommunity/brs/blob/main/src/brsTypes/nodes/NodeFactory.ts). This will allow instances of your new node to be created dynamically when it is encountered in XML or BrightScript code.
1. Add a test case for your Typescript class in [the nodes test directory](https://github.com/rokucommunity/brs/tree/master/test/brsTypes/nodes). Use the existing component test files in that directory as a model for what your test should look like.
1. Add an end-to-end test case.
- Create a file in [the end-to-end directory](https://github.com/rokucommunity/brs/tree/master/test/e2e) called `<insert component name>.brs`. In the file, write BrightScript code that exercises your component functionality.
- Add an XML file to the [the components test directory](https://github.com/rokucommunity/brs/tree/master/test/brsTypes/components) that uses your component.
- Create a file in [the end-to-end directory](https://github.com/rokucommunity/brs/tree/master/test/e2e) called `<insert node name>.brs`. In the file, write BrightScript code that exercises your node functionality.
- Add an XML file to the [the components test directory](https://github.com/rokucommunity/brs/tree/master/test/brsTypes/components) that uses your node.
- Add a test block to [BrsComponents.test.js](https://github.com/rokucommunity/brs/blob/main/test/e2e/BrsComponents.test.js). In this block, verify that the code from your XML and Brightscript files is behaving as expected.
2 changes: 1 addition & 1 deletion src/brsTypes/Callable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Brs from ".";
import * as Expr from "../parser/Expression";
import { Scope } from "../interpreter/Environment";
import { Location } from "../lexer";
import { tryCoerce } from "./coercion";
import { tryCoerce } from "./Coercion";
import { generateArgumentMismatchError } from "../interpreter/ArgumentMismatch";

/** An argument to a BrightScript `function` or `sub`. */
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/brsTypes/components/BrsObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RoArray } from "./RoArray";
import { RoList } from "./RoList";
import { RoByteArray } from "./RoByteArray";
import { RoDateTime } from "./RoDateTime";
import { Timespan } from "./Timespan";
import { RoTimespan } from "./RoTimespan";
import { createNodeByType } from "./RoSGNode";
import { RoRegex } from "./RoRegex";
import { RoXMLElement } from "./RoXMLElement";
Expand Down Expand Up @@ -91,7 +91,7 @@ export const BrsObjects = new BrsObjectsMap([
["roList", (_: Interpreter) => new RoList([])],
["roByteArray", (_: Interpreter) => new RoByteArray()],
["roDateTime", (_: Interpreter) => new RoDateTime()],
["roTimespan", (_: Interpreter) => new Timespan()],
["roTimespan", (_: Interpreter) => new RoTimespan()],
["roDeviceInfo", (_: Interpreter) => new RoDeviceInfo()],
[
"roSGNode",
Expand Down
17 changes: 7 additions & 10 deletions src/brsTypes/components/RoSGNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { RoAssociativeArray } from "./RoAssociativeArray";
import { RoArray } from "./RoArray";
import { AAMember } from "./RoAssociativeArray";
import { ComponentDefinition, ComponentNode } from "../../componentprocessor";
import { ComponentFactory, BrsComponentName } from "./ComponentFactory";
import { NodeFactory, BrsNodeType } from "../nodes/NodeFactory";
import { Environment } from "../../interpreter/Environment";
import { roInvalid } from "./RoInvalid";
import type * as MockNodeModule from "../../extensions/MockNode";
Expand Down Expand Up @@ -1757,10 +1757,10 @@ export function createNodeByType(interpreter: Interpreter, type: BrsString): RoS
return new mock.MockNode(maybeMock, type.value);
}

// If this is a built-in component, then return it.
let component = ComponentFactory.createComponent(type.value as BrsComponentName);
if (component) {
return component;
// If this is a built-in node component, then return it.
let node = NodeFactory.createComponent(type.value as BrsNodeType);
if (node) {
return node;
}

let typeDef = interpreter.environment.nodeDefMap.get(type.value.toLowerCase());
Expand All @@ -1783,11 +1783,8 @@ export function createNodeByType(interpreter: Interpreter, type: BrsString): RoS
// Start from the "basemost" component of the tree.
typeDef = typeDefStack.pop();

// If this extends a built-in component, create it.
let node = ComponentFactory.createComponent(
typeDef!.extends as BrsComponentName,
type.value
);
// If this extends a built-in node component, create it.
let node = NodeFactory.createComponent(typeDef!.extends as BrsNodeType, type.value);

// Default to Node as parent.
if (!node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import customParseFormat from "dayjs/plugin/customParseFormat";

export class Timespan extends BrsComponent implements BrsValue {
export class RoTimespan extends BrsComponent implements BrsValue {
readonly kind = ValueKind.Object;
private markTime = Date.now();

Expand Down
32 changes: 16 additions & 16 deletions src/brsTypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ export * from "./BrsInterface";
export * from "./Callable";
export * from "./components/BrsComponent";
export * from "./components/RoDeviceInfo";
export * from "./components/ComponentFactory";
export * from "./components/RoArray";
export * from "./components/RoList";
export * from "./components/RoByteArray";
export * from "./components/RoDateTime";
export * from "./components/RoAssociativeArray";
export * from "./components/Timespan";
export * from "./components/RoTimespan";
export * from "./components/BrsObjects";
export * from "./components/RoRegex";
export * from "./components/RoXMLElement";
Expand All @@ -53,23 +52,24 @@ export * from "./components/RoLongInteger";
export * from "./components/RoInvalid";
export * from "./components/RoSGNodeEvent";
export * from "./components/RoSGNode";
export * from "./components/Group";
export * from "./components/Scene";
export * from "./components/MiniKeyboard";
export * from "./components/TextEditBox";
export * from "./components/LayoutGroup";
export * from "./components/Rectangle";
export * from "./components/Label";
export * from "./components/Font";
export * from "./components/Poster";
export * from "./components/ArrayGrid";
export * from "./components/MarkupGrid";
export * from "./components/ContentNode";
export * from "./components/Timer";
export * from "./components/RoAppInfo";
export * from "./components/RoPath";
export * from "./nodes/NodeFactory";
export * from "./nodes/Group";
export * from "./nodes/Scene";
export * from "./nodes/MiniKeyboard";
export * from "./nodes/TextEditBox";
export * from "./nodes/LayoutGroup";
export * from "./nodes/Rectangle";
export * from "./nodes/Label";
export * from "./nodes/Font";
export * from "./nodes/Poster";
export * from "./nodes/ArrayGrid";
export * from "./nodes/MarkupGrid";
export * from "./nodes/ContentNode";
export * from "./nodes/Timer";
export * from "./Boxing";
export * from "./coercion";
export * from "./Coercion";

/**
* Determines whether or not the given value is a number.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { Group } from "./Group";

export class ArrayGrid extends Group {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FieldModel, Field, RoSGNode } from "./RoSGNode";
import { FieldModel, Field, RoSGNode } from "../components/RoSGNode";
import { BrsType, toAssociativeArray } from "..";
import { ValueKind, BrsString, BrsBoolean } from "../BrsType";
import { Interpreter } from "../../interpreter";
import { Int32 } from "../Int32";
import { Callable, StdlibArgument } from "../Callable";
import { RoArray } from "./RoArray";
import { RoArray } from "../components/RoArray";

export class ContentNode extends RoSGNode {
readonly defaultFields: FieldModel[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RoSGNode, FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { RoSGNode, FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";

export class Font extends RoSGNode {
readonly defaultFields: FieldModel[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RoSGNode, FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { RoSGNode, FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";

export class Group extends RoSGNode {
readonly defaultFields: FieldModel[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { Group } from "./Group";

export class Label extends Group {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { Group } from "./Group";

export class LayoutGroup extends Group {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { ArrayGrid } from "./ArrayGrid";

export class MarkupGrid extends ArrayGrid {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FieldModel } from "./RoSGNode";
import { FieldModel } from "../components/RoSGNode";
import { Group } from "./Group";
import { AAMember } from "./RoAssociativeArray";
import { AAMember } from "../components/RoAssociativeArray";
import { BrsString } from "../BrsType";
import { TextEditBox } from "./TextEditBox";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import {
Scene,
MiniKeyboard,
TextEditBox,
BrsComponent,
} from "..";

export enum BrsComponentName {
export enum BrsNodeType {
Node = "Node",
Group = "Group",
LayoutGroup = "LayoutGroup",
Expand All @@ -34,79 +33,76 @@ export enum BrsComponentName {
}

// TODO: update with more components as they're implemented.
export class ComponentFactory {
private static additionalComponents = new Map<string, (name: string) => RoSGNode>();
export class NodeFactory {
private static additionalNodes = new Map<string, (name: string) => RoSGNode>();

/**
* Adds additional components types to the factory, so other software can extend brs if necessary.
* Adds additional node/component types to the factory, so other software can extend brs if necessary.
* This would allow other software using this to add other node/component types at runtime
* For example, adding custom implementations of the built-in types, or
* adding additional types (PinPad, BusySpinner, etc) that aren't here yet
*
* @static
* @param types Array of pairs of [componentTypeName, construction function], such that when a given componentType is requested, the construction function is called and returns one of those components
* @param types Array of pairs of [nodeTypeName, construction function], such that when a given nodeType is requested, the construction function is called and returns one of those components
*/
public static addComponentTypes(types: [string, (name: string) => RoSGNode][]) {
types.forEach(([componentType, ctor]) => {
this.additionalComponents.set(componentType.toLowerCase(), ctor);
public static addNodeTypes(types: [string, (name: string) => RoSGNode][]) {
types.forEach(([nodeType, ctor]) => {
this.additionalNodes.set(nodeType.toLowerCase(), ctor);
});
}

public static createComponent(
componentType: BrsComponentName | string,
componentName?: string
nodeType: BrsNodeType | string,
nodeName?: string
): RoSGNode | undefined {
let name = componentName || componentType;
const additionalCtor = this.additionalComponents.get(componentType?.toLowerCase());
let name = nodeName || nodeType;
const additionalCtor = this.additionalNodes.get(nodeType?.toLowerCase());
if (additionalCtor) {
return additionalCtor(name);
}
switch (componentType) {
case BrsComponentName.Group:
switch (nodeType) {
case BrsNodeType.Group:
return new Group([], name);
case BrsComponentName.LayoutGroup:
case BrsNodeType.LayoutGroup:
return new LayoutGroup([], name);
case BrsComponentName.Node:
case BrsNodeType.Node:
return new RoSGNode([], name);
case BrsComponentName.Rectangle:
case BrsNodeType.Rectangle:
return new Rectangle([], name);
case BrsComponentName.Label:
case BrsNodeType.Label:
return new Label([], name);
case BrsComponentName.Font:
case BrsNodeType.Font:
return new Font([], name);
case BrsComponentName.Poster:
case BrsNodeType.Poster:
return new Poster([], name);
case BrsComponentName.ArrayGrid:
case BrsNodeType.ArrayGrid:
return new ArrayGrid([], name);
case BrsComponentName.MarkupGrid:
case BrsNodeType.MarkupGrid:
return new MarkupGrid([], name);
case BrsComponentName.ContentNode:
case BrsNodeType.ContentNode:
return new ContentNode(name);
case BrsComponentName.Timer:
case BrsNodeType.Timer:
return new Timer([], name);
case BrsComponentName.Scene:
case BrsNodeType.Scene:
return new Scene([], name);
case BrsComponentName.MiniKeyboard:
case BrsNodeType.MiniKeyboard:
return new MiniKeyboard([], name);
case BrsComponentName.TextEditBox:
case BrsNodeType.TextEditBox:
return new TextEditBox([], name);
default:
return;
}
}

/**
* Checks to see if the given component type can be resolved by the Factory
* Checks to see if the given node type can be resolved by the Factory
* That is, if it is a built in type or has been added at run time.
*
* @static
* @param componentType The name of component to resolve
* @param nodeType The name of node to resolve
* @returns {boolean} true if that type is resolvable/constructable, false otherwise
*/
public static canResolveComponentType(componentType: BrsComponentName | string): boolean {
return (
this.additionalComponents.has(componentType?.toLowerCase()) ||
componentType in BrsComponentName
);
public static canResolveComponentType(nodeType: BrsNodeType | string): boolean {
return this.additionalNodes.has(nodeType?.toLowerCase()) || nodeType in BrsNodeType;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { Group } from "./Group";

export class Poster extends Group {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldModel } from "./RoSGNode";
import { AAMember } from "./RoAssociativeArray";
import { FieldModel } from "../components/RoSGNode";
import { AAMember } from "../components/RoAssociativeArray";
import { Group } from "./Group";

export class Rectangle extends Group {
Expand Down
Loading
Loading