Skip to content

Commit

Permalink
Merge pull request #371 from georgejecook/fix/optional-command
Browse files Browse the repository at this point in the history
Fix/optional command
  • Loading branch information
georgejecook authored May 10, 2023
2 parents 8707ebd + e775456 commit 35aa224
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 73 deletions.
133 changes: 133 additions & 0 deletions docs/command-and-launch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
## Overview

Controlling a sequence of events can be difficult to write, test and maintain. Maestro makes this easy with command sequences, which manage various commands.

Maestro also has a specialized CommandSequence, called LaunchSequence, which is tailored for initializing a maestro framework app

### CommandSequence

The Command sequence is a special node, which acts like a task, so it can be created and observed by using m.createTask("MyCommandSequence")

To make a command sequence, extend the CommandSequence and implement the `createCommands` function, to return an array of commands, e.g.


```
namespace mc.app
@node("mc_LaunchSequence", "Group")
class LaunchSequence
protected override function createCommands()
return [new mc.SequentialCommand("Launch Sequence Commands", [
new mc.Command("Init IOC", m.initializeIOC)
new mc.OptionalSequentialCommand("Maestro Initialization", m.isIncludingView, m.getMaestroInitializationCommands())
' new mc.SequentialCommand("Custom Launch", m.getCustomCommands())
new mc.SequentialCommand("Custom Launch", m.getCustomCommands())
]
)]
end function
```

For basic commands (e.g. `mc.Command`), you _must_ invoke m.finishCommand(command) on your launch sequence class, to inform maestro that the command is finished.

The CommandSequence will do the rest.

### Specifying Commands

- Command
- This is the regular command class. Construct a new command with a name, and function to execute
- TaskCommand
- Special command that will execute the task, and finish when the result is returned
- SequentialCommand
- Executes the provided array of tasks, in sequence, completing when the last is finished
- ParallelCommand
- Executes the provided array of tasks, all at the same time, completing when all are finished
- OptionalCommand
- A command which will run or not, based on the boolean flag
- OptionalSequentialCommand
- An Sequential command which will run or not, based on the boolean flag
- OptionalParallelCommand
- An Parallel command which will run or not, based on the boolean flag
- CommandProvider
- A command _factory_ this is useful for when you need to provide a command, later in your sequence execution; but the values for it might not yet be ready. e.g. if you have a command you want to run, after you have loaded some server data, then you can defer the command creation till the moment it is needed.

All commands take an options aa argument, which allows for further command customization. Options are

| Option | Commands | Description |
|-----------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ignoreFailure | All | Will continue if the comand fails |
| isWaitingResult | TaskCommand | If false, will mark the task as complete straight away. Default is true |
| onComplete | TaskCommand | Handler function that will be called with mc.Result the task provided, allowing for processing of the task. You can return true or false, to change the result.isOk value, of your result |


### Example commands
```
new mc.SequentialCommand("Commands", [
new mc.Command("A", m.doMockCommandWork)
new mc.OptionalSequentialCommand("B", false, [
mc.Command("B_1", m.doMockCommandWork)
mc.Command("B_2", m.doMockCommandWork)
])
new mc.Command("C", m.doMockCommandWork)
new mc.CommandProvider("D", m.getProvidedCommand)
new mc.Command("E", m.doMockCommandWork)
new mc.CommandProvider("F", m.getInvalidProvidedCommand)
new mc.Command("G", m.doMockCommandWork),
new mc.OptionalSequentialCommand("H", false, [
mc.Command("H_1", m.doMockCommandWork)
mc.Command("H_2", m.doMockCommandWork)
])
]
)
```


## LaunchSequence

A LaunchSequence has some conventions built into it, that help repeatable initialize an app.To use, simply override the getCustomCommands method, returning an array of commands.

e.g.

```
protected function getCustomCommands() as mc.types.array
'override point to provide sequential custom commands
return [
new mc.TaskCommand("Load Data", "LoadDataTask")]
end function
```

### Maestro initialization
To assist with rapid app development, there is a default maestro initialization function which is called for configuring maestro, which can be overridden. To execute your own application launch commands,

```
protected function getMaestroInitializationCommands() as mc.types.array
return [
new mc.Command("Create FontManger", m.createFontManger)
new mc.Command("Load Styles", m.loadStyles)
new mc.Command("Setup Maestro", m.setupMaestro)
new mc.Command("Initialize ComponentPool", m.initializeComponentPool)
new mc.Command("Prebake Cells", m._prebakeViews)
]
end function
```

### IOC initialization

The LaunchSequence will take a default instantiation of the IOC when it starts up, which can be (and should be) overridden, to return your correct IOC instantiation, e.g.

```
protected function initializeIOC(command as mc.Command)
m.log.info("initializing the IOC container")
mioc.createContainer()
m.createIOCInstance("mc_delayedTaskManager", "delayedTaskManager")
m.setInstance("manifest", m.loadManifest())
m.finishCommand(command)
end function
```




8 changes: 5 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ title: Maestro-roku

### Maestro Makes roku development easier, especially for experienced software engineers from other platforms:

I believe that experienced developers from android, ios, c#, web, node, etc, should be able to be productive on a roku app in no more than a week, just as they would on any other platform. So I wrote maestro to make that possible.
Experienced developers from android, ios, c#, web, node, etc, will be able to be productive on a maestro roku app in no more than a week, just as they would on any other platform.

## Maestro is built to:

Expand All @@ -29,10 +29,11 @@ I believe that experienced developers from android, ios, c#, web, node, etc, sho
- Write code that can be tested and breakpoint debugged, outside of SG views (which are slow as hell, and prone to crashing when breakpoint debugging)


## Maestro is easy to use:
## Maestro is easy to use and powerful:

- Delivered as ropm module for easy installation
- Has sample app, which is ready to roll for production ready roku apps
- Has a transpiler plugin which adds additional language comfort

## Maestro is aligned with community best practices and tools

Expand All @@ -54,7 +55,7 @@ Been in production for > 2 years at:
- applicaster, and their various clients
- smithsonian
- corco
- other clients (names pending permission ;) )
- NBA

# Contributing:

Expand Down Expand Up @@ -96,5 +97,6 @@ Been in production for > 2 years at:
- [Getting started](/docs/About:-Getting-started.md)
- [Callbacks](/docs/List-Component:-Callbacks.md)
- [CustomCells](/docs/List-Component:-CustomCells.md)
* [Command Sequences and controlling application launch] (/docs/command-and-launch.md)
* [Debugging](/docs/Debugging.md)
* [API documentation](/docs/API-Docs.md)
11 changes: 10 additions & 1 deletion src/source/core/Command.bs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace mc
m.childCommands = []
if mc.isArray(commands)
for each command in commands
if command <> invalid
if command <> invalid and not (command.isOptional = true and command.isActive = false)
command.parentCommand = m
m.childCommands.push(command)
end if
Expand Down Expand Up @@ -79,6 +79,15 @@ namespace mc
end function

end class
class OptionalCommand extends mc.Command
isOptional = true
isActive = false

function new(name as string, isActive as boolean, func as function, options = {} as mc.types.assocarray)
super(name, func, options)
m.isActive = isActive
end function
end class
class OptionalSequentialCommand extends mc.SequentialCommand
isOptional = true
isActive = false
Expand Down
26 changes: 12 additions & 14 deletions src/source/core/Command.spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")
end function

@it("filters invalid commands")
Expand All @@ -46,8 +46,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")
end function


Expand Down Expand Up @@ -146,8 +146,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")

end function

Expand All @@ -174,8 +174,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Expand All @@ -200,8 +200,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")

end function

Expand All @@ -228,8 +228,8 @@ namespace tests
m.assertEqual(m.command.childCommands[0].id, "c1")
m.assertEqual(m.command.childCommands[1].id, "c2")

m.assertEqual(c1.parentCommand, m.command)
m.assertEqual(c2.parentCommand, m.command)
m.assertEqual(c1.parentCommand.name, "test")
m.assertEqual(c2.parentCommand.name, "test")
end function

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Expand Down Expand Up @@ -278,7 +278,6 @@ namespace tests
m.command.moveNext()
m.assertInvalid(m.command.currentCommand)
m.assertEqual(m.command.commandIndex, 2)
m.assertTrue(m.command.isFinished)
end function

@it("is complete when there are no tasks")
Expand All @@ -290,7 +289,6 @@ namespace tests
m.command.moveNext()
m.assertInvalid(m.command.currentCommand)
m.assertEqual(m.command.commandIndex, 0)
m.assertTrue(m.command.isFinished)
end function

end class
Expand Down
5 changes: 5 additions & 0 deletions src/source/core/CommandSequence.bs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ namespace mc
return
end if

if command.isOptional = true and not command.isActive
m.log.info("skipping inactive optional command", command.name)
return
end if

if command.isProvider = true
m.log.verbose("this command is a provider, using the provided command")
providedCommand = mc.apply(command.func, [command], m)
Expand Down
Loading

0 comments on commit 35aa224

Please sign in to comment.