Looping via Command Map pairs

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 Command Map pairs

Postby bhiga » Wed Aug 17, 2005 10:31 pm

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?
This is an alternate method that works similarly to the recursive method presented earlier.

Dependencies
This structure utilizes command maps, MLConditional, MLMath and RunCmdMap.

Important Notes
The looping actually occurs in three parts.
The first part sets up the looping index variables.
The second part consists of the loop check (loop control) and a call to the loop body.
The third part is the loop body which performs the desired actions and calls the loop control.

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. If you start {{LoopIndex}} at 1, this needs to be one more than the number of times the loop should execute. If you start {{LoopIndex}} at 0, this should be the number of times the loop should execute.

Command structure
Step 1: Set up the looping code (loop body)
First we add the looping code 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!
RunCmdMap|name_of_loop_control_command_map


Step 2: Set up the loop check code (loop control)
We also add the loop check code (loop control) as a command map. Command maps may be accessed via the MLServer interface in Utilities|Map Commands

Code: Select all

MLServeCmd.MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|name_of_loop_body_command_map


Step 3: 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!
name_of_loop_control_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.Control

Code: Select all

MLServeCmd.MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Body


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

Code: Select all

MLServeCmd.Macro|
MLMath|ADD~Foo~{{Foo}}~_~{{LoopIndex}}!
MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
RunCmdMap|Loop.Control


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.Control


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.Control command map:
MLServeCmd.MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Body
If the {{LoopIndex}} value ist equal to the {{LoopBoundary}} value, then we are done with the loop, so we do nothing. Otherwise we should continue executing the loop, so execute the the Loop.Body command map.

Loop.Body 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.

RunCmdMap|Loop.Control
This statement executes (passes control back to) the Loop.Body command map which will then again check the {{LoopIndex}} against the {{LoopBoundary}} and if the {{LoopIndex}} hasn't reached {{LoopBoundary}} then it will execute the Loop.Body command map again.

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.Control
Now that we have our loop variables set, we run the loop by executing the Loop.Control command map, which will then bounce back-and-forth between Loop.Control and Loop.Body, incrementing {{LoopIndex}} along the way until {{LoopIndex}} equals {{LoopBoundary}}.


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.Control command map gets run

In the first run of Loop.Control...
4. Loop.Control checks if {{LoopIndex}} equals {{LoopBoundary}}. It does not (0 != 10), so it executes Loop.Body.

In the first run of Loop.Body...
5. 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
6. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 1
7. The Loop.Control command map is run.

On the second run of Loop.Control...
8. Loop.Control checks if {{LoopIndex}} equals {{LoopBoundary}}. It does not (1 != 10), so it executes Loop.Body.

On the second run of Loop.Body...
9. An underscore and the value of {{LoopIndex}} gets appended to Foo, and {{LoopIndex}} is 1, so {{Foo}} = _0_1
10. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 2
11. The Loop.Control command map is run.


On the third run of Loop.Control...
10. Loop.Control checks if {{LoopIndex}} equals {{LoopBoundary}}. It does not (2 != 10), so it executes Loop.Body.

On the third run of Loop.Body...
11. An underscore and the value of {{LoopIndex}} gets appended to Foo, and {{LoopIndex}} is 2, so {{Foo}} = _0_1_2
12. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 3
13. The Loop.Control command map is run.
.
.
.
On the tenth run of Loop.Control...
38. Loop.Control checks if {{LoopIndex}} equals {{LoopBoundary}}. It does not (2 != 10), so it executes Loop.Body.

On the tenth run of Loop.Body...
39. 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
40. {{LoopIndex}} gets incremented by 1, so now {{LoopIndex}} is 10
41. The Loop.Control command map is run.

On the eleventh run of Loop.Control...
42. Loop.Control checks if {{LoopIndex}} equals {{LoopBoundary}}. It does, so 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.Control command map code
First, construct the control part of the loop

Code: Select all

MLServeCmd.MLConditional|IsEqual##{{LoopIndex}}##{{LoopBoundary}}####RunCmdMap|Loop.Body


Loop.Body command map code
Second, construct the functional part of the the loop

Code: Select all

MLServeCmd.Macro|
SetVariable|button{{LoopIndex}}~False!
MLMath|ADD~LoopIndex~{{LoopIndex}}~1!
RunCmdMap|Loop.Control


Code to launch the loop

Code: Select all

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


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.Control


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"

Loop.Control command map code (decremental loop)

Code: Select all

MLServeCmd.MLConditional|IsEqual##{{LoopIndex}}##0####RunCmdMap|Loop.Body


Loop.Body command map code (decremental loop)

Code: Select all

MLServeCmd.Macro|
commands_to_perform_in_loop_go_here
MLMath|SUBTRACT~LoopIndex~{{LoopIndex}}~1!
RunCmdMap|Loop.Control


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

Code: Select all

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


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:
This method is a few more steps than the recursive method I presented earlier.

EDIT: Switched implemented from MLRedirect trick to using RunCmdMap - much better performance!

Brandon
Last edited by bhiga on Thu Aug 18, 2005 11:00 am, edited 1 time 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 Aug 18, 2005 2:24 am

Great stuff, Brandon! Thanks! =D>

Why the redirect trick instead of the RunCmdMap function? I've had better luck with RunCmdMap than the redirect trick; it's a lot faster and doesn't seem to result in as many weird issues. :D

User avatar
bhiga
Expert
Expert
Posts: 854
Joined: Tue Mar 08, 2005 10:28 pm
Location: San Jose, CA
Contact:

Postby bhiga » Thu Aug 18, 2005 10:26 am

Thanks!

I just retrofit one of my loops to use RunCmdMap and it's much better!

It's a simple search-and-replace of MLRedirect|localhost::5004:: with RunCmdMap| to do it that way.

RunCmdMap is a bit hard to find though...
http://www.cinemaronlineforums.com/foru ... php?t=4583
- Brandon

My MainLobby stuff (plug-ins, screenshots, etc)


Return to “MLServeCmd Examples”

Who is online

Users browsing this forum: No registered users and 1 guest