Looping via recursive Command Maps

If you have created a unique MLServeCmd that performs a unique task, please post it here so others can learn from them.
User avatar
bhiga
Expert
Expert
Posts: 854
Joined: Tue Mar 08, 2005 10:28 pm
Location: San Jose, CA
Contact:

Looping via recursive Command Maps

Postby bhiga » Thu Apr 07, 2005 11:34 am

Description
Often times you may need to perform the same or similar task repeatedly.
For example, you may want to increase the volume on your receiver by 5 steps. Without a looping construct, you'd have to send the "Vol +1" command 5 times manually, or duplicate the command to send "Vol + 1" 5 times in your MLServeCmd line. While this seems okay for the occassional command, what if you wanted to add a button that increased the volume by 20 steps? How about changing the volume based on a variable?

Dependencies
This structure utilizes command maps, MLConditional, MLMath, and optionally MLPause.

Important Notes
The looping actually occurs in two parts.
The first piece is the looping portion consisting of the loop check and commands that get repeated.
The second piece is the "setup" of the looping variables and launching the loop for the first pass.

Looping variables
We're going to use some variables to keep track of the looping itself. You may use whatever variables you want, but I will be using:

{{LoopIndex}} - this is the "counter" for the loop. It changes values each time the loop is executed and also controls when the loop terminates. It is very important that no other commands within the loop or launched by the loop alter this variable (except in very deliberate circumstances, like terminating a loop prematurely) otherwise you run the risk of causing an endless loop.

{{LoopBoundary}} - this is the value that LoopIndex must reach for the loop to end.

Command structure
Step 1: Set up the looping code
First we add the looping code in as a command map. Command maps may be accessed via the MLServer interface in Utilities|Map Commands

Code: Select all

MLServeCmd.Macro|
commands_to_perform_in_loop_go_here
MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|name_of_loop_command_map


Step 2: Set up the loop variables and begin the loop
This code gets put into a button, macro or command map where you want to start the loop.

Code: Select all

MLServeCmd.Macro|
SetVariable~LoopIndex~0!
SetVariable~LoopBoundary~10!
RunCmdMap|name_of_loop_command_map


Sample for loop that runs 10 times and sets Foo to _0_1_2_3_4_5_6_7_8_9

This code should be saved in a command map named Loop.Test1

Code: Select all

MLServeCmd.Macro|
MLMath|ADD~Foo~{{Foo}}~_~{{LoopIndex}}!
MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Test1


This code gets put into a button, macro or command map where you want to start the loop.

Code: Select all

MLServeCmd.Macro|
SetVariable~LoopIndex~0!
SetVariable~LoopBoundary~10!
RunCmdMap|Loop.Test1


If you've done everything correctly (and I typed/copied everything correctly), the variable Foo should end up as the string
_0_1_2_3_4_5_6_7_8_9

So what's really going on in there?
Loop.Test1 command map:
MLServeCmd.Macro|
Macro. If you used the Builder, you wouldn't need to put this in because it'd put it in for you.

MLMath|ADD~Foo~{{Foo}}~_~{{LoopIndex}}!
This is the actual statement that happens "in" the loop. Here we're taking the value of the variable Foo, and concatenating it with an underscore and the value of LoopIndex.
On the first pass, it adds _1 to the value of {{Foo}}. On the second pass, it adds _2 to the value of Foo, etc.


MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
This is part of the loop control. We increment the loop index here to reflect the value on the next pass.

MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Test1
This statement is the other part of the loop control. We check whether the value of {{LoopIndex}} is equal to {{LoopBoundary}}. If so, we don't do anything. If not, we use "call" the looping code again.
Technically this would be considered recursion in most languages as the same function is calling itself, but I believe in the case of MLServer it just queues commands, so this is a queuing loop.


Loop variable setup and begin loop:
MLServeCmd.Macro|
You know what this is.

SetVariable~LoopIndex~0!
This sets the initial value of LoopIndex. Since this is an incremental loop and we're incrementing LoopIndex at the end of loop, we should start at zero. Otherwise if you start at 1, we'll loop one time less than LoopBoundary.

SetVariable~LoopBoundary~10!
Here we set the number of times we're going to run the loop. The loop will really run for LoopBoundary - (initial value of LoopIndex) times, so as long as we start LoopIndex at 0, we'll run the loop for LoopBoundary times.

RunCmdMap|Loop.Test1
Now that we have our loop variables set, we run the loop.


Umm... I got lost...
Let's trace through the steps.

When the loop variable setup occurs:
1. {{LoopIndex}} gets set to 0
2. {{LoopBoundary}} gets set to 10
3. Loop.Test1 command map gets run

In the first run of Loop.Test1...
4. An underscore and the value of {{LoopIndex}} gets appended to Foo.
{{Foo}} is currently empty (maybe we should have initialized it but let's assume it was empty), and {{LoopIndex}} is 0, so {{Foo}} = _0
5. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 1
6. The conditional checks whether {{LoopIndex}} equals {{LoopBoundary}}. {{LoopIndex}} = 1, {{LoopBoundary}} = 10, so that test is False. Thus, we call Loop.Test1 again.

On the second run of Loop.Test1...
7. An underscore and the value of {{LoopIndex}} gets appended to Foo, and {{LoopIndex}} is 1, so {{Foo}} = _0_1
8. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 2
9. The conditional checks whether {{LoopIndex}} equals {{LoopBoundary}}. {{LoopIndex}} = 2, {{LoopBoundary}} = 10, so that test is False. Thus, we call Loop.Test1 again.

On the third run of Loop.Test1...
10. An underscore and the value of {{LoopIndex}} gets appended to Foo, and {{LoopIndex}} is 2, so {{Foo}} = _0_1_2
11. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 3
12. The conditional checks whether {{LoopIndex}} equals {{LoopBoundary}}. {{LoopIndex}} = 3, {{LoopBoundary}} = 10, so that test is False. Thus, we call Loop.Test1 again.
.
.
.
On the tenth run of Loop.Test1...
31. An underscore and the value of {{LoopIndex}} gets appended to Foo, and {{LoopIndex}} is 9, so {{Foo}} = _0_1_2_3_4_5_6_7_8_9
32. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 10
33. The conditional checks whether {{LoopIndex}} equals {{LoopBoundary}}. {{LoopIndex}} = 10, {{LoopBoundary}} = 10, so that test is True. Thus, we don't do anything and now the loop is complete.


Okay! Now for more useful things...

Let's set the value of {{button0}} through {{button9}} to False.

Loop.Test2 command map code
First, construct the command map for the loop

Code: Select all

MLServeCmd.Macro|
SetVariable|button{{LoopIndex}}~False!
MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Test2


Code to launch the loop

Code: Select all

MLServeCmd.Macro|
SetVariable|LoopIndex~0!
SetVariable|LoopBoundary~10!
RunCmdMap|Loop.Test2


Remember that we're starting from 0, so we stop at 10 because the loop stops when LoopIndex is one less than LoopBoundary.


But what if I don't want to start at zero?
Okay, no problem...
To do the same thing as above, but go from {{button1}} to {{button10}}, we just start LoopIndex at 1, and set LoopBoundary at 11. Remember that the loop runs for LoopBoundary - (initial value of LoopIndex) iterations, so 11 - 1 = 10, which is correct.

Code to launch the loop starting from 1 going to 10.

Code: Select all

MLServeCmd.Macro|
SetVariable|LoopIndex~1!
SetVariable|LoopBoundary~11!
RunCmdMap|Loop.Test2


Can I go backward?
Sure, you can construct the loop to decrement instead of increment. We'll have to change our code a little.

This version will "loop down to 1"

Looping command map code (decremental loop)

Code: Select all

MLServeCmd.Macro|
commands_to_perform_in_loop_go_here
MLMath|SUBTRACT~LoopIndex~{{LoopIndex}}~1!
MLConditional|IsEqual##{{LoopIndex}}##0####RunCmdMap|name_of_loop_command_map


Set up the loop variables and begin the loop (decremental loop)

Code: Select all

MLServeCmd.Macro|
SetVariable~LoopBoundary~10!
SetVariable~LoopIndex~{{LoopBoundary}}!
RunCmdMap|name_of_loop_command_map


Note that in a decremental loop, since we're running until {{LoopIndex}} is 0, we really don't need {{LoopBoundary}} at all and we can start with {{LoopIndex}} as the number of times we want to run the loop. I just left the "copy" from LoopBoundary to LoopIndex in as a cleanliness measure.

Additional Notes:
I'm not sure how "safe" it is to run this in large long loops. If I am correct that MLServer just queues commands and doesn't have a stack like conventional programming languages, then it should be fine.

I'm also unsure whether commands are guaranteed to run in-order, or if MLServer runs them asynchronously. In my limited testing, commands are run in the order they were queued.

If your processing relies on the loop's completion, you may need to include an MLPause after launching the loop to have your code wait for the loop to finish before continuing.

EDIT: Switched implementation from MLRedirect to RunCmdMap resulting in much better performance.

Brandon
Last edited by bhiga on Thu Aug 18, 2005 11:08 am, edited 3 times in total.
- Brandon
My MainLobby stuff (plug-ins, screenshots, etc)

User avatar
gregoryx
Simply Incredible
Simply Incredible
Posts: 6599
Joined: Tue Sep 30, 2003 10:15 pm
Location: Newport Beach, CA
Contact:

Postby gregoryx » Thu Apr 07, 2005 5:04 pm

hehehe... I should have read the next post to see what you meant by looping! :wink:

I'll have to save this for review tonight... it's a lot to bite off but looks very promising!

Thanks!

User avatar
gregoryx
Simply Incredible
Simply Incredible
Posts: 6599
Joined: Tue Sep 30, 2003 10:15 pm
Location: Newport Beach, CA
Contact:

Postby gregoryx » Fri Apr 08, 2005 12:09 am

Clever! Good find and great documentation!
:D


Return to “MLServeCmd Examples”

Who is online

Users browsing this forum: No registered users and 1 guest