-
Notifications
You must be signed in to change notification settings - Fork 1
Chip Execution Queueing
Before I dip into this, massive shoutout to the people in the Cylon Discord. Past conversations about this topic and examples provided in discord gave me the threads I needed to start experimenting in-game to sort all this out. Specifically Nyefari's, highCard's, Azurethi's, Firestar99's, and Antani's comments were key.
IPS requires certain math done in a specific order.
- Get signal strengths
- Calculate
RawX. - Calculate
RawY, depends onRawX. - Calculate
RawZ, depends onRawXandRawY. - Calculate
RotatedX, RotatedY, RotatedZ, depends onRawX, RawY, RawZ. - Offset coordinates, depends on
RotatedX, RotatedY, RotatedZ.
We have a update speed of 0.6 seconds on the single-chip IPS right now, and chances are that will never decrease. So if we want to try and push that update speed lower then we need to separate the steps onto different chips.
From what I understand, chips run on a mean 0.2 second tick. This means that over a given second, the mean execution time for each line is 0.2 seconds. And that means that the actual execution time per line can vary slightly. For instance, line 1 may execute with a time of 0.18 seconds and the second line could execute with a time of 0.22 seconds.
In addition, the order different chips are executed in a given tick is chosen randomly. So not only will the chips not stay in sync time-wise, but they will also not be reliably executed one after another as we need. So we have to force them to behave how we want. To do that, we use ChipWait.
As per the wiki, YOLO chips have a ChipWait field that allows us to control execution of a chip.
- Negative values = pause execution (note this 'paused time' must last for at least 1 tick)
-
0= execute - Positive values = delay execution for the set number of ticks
Using that, we can daisy-chain chips so one triggers another in a specific order. For example,
// Chip 1, Chipwait1
:o = "Chip 1 Running" :Chipwait2=0 :Chipwait1=-1 goto1
// Chip 2, Chipwait2
:o = "Chip 2 Running" :Chipwait3=0 :Chipwait2=-1 goto1
// Chip 3, Chipwait3
:o = "Chip 3 Running" :Chipwait1=0 :Chipwait3=-1 goto1
The problem with this is once a chipwait has been set to 0, it will execute on the next tick. As a result, we've split the code across 3 chips but it still takes 3 ticks to execute all the code. To actually make progress, we need to dip into some shenanigans.
Enter the Memory Relay. Memory Relays take an input memory chip and an output memory chip. Values set in the input memory chip are transferred over to the output memory chip, based on memory field index and regardless of field name. This means if the first field in the input memory chip is named apples and the first field in the output memory chip is named oranges, it doesn't matter. Any changes to apples will be applied to oranges.
Now the black magic part. From what I understand (and I could be wrong), when you set the chipwait of a chip to 0, the execution of that chip is placed into a background game engine execution stack of some kind. If you set another chipwait, then that chip is placed on the top of the stack.
So, we can use the Memory Relay to set chipwait values in order, which then causes the respective chips to execute in order. The trick is to use the Memory Relay to handle the daisy chaining.
Lets walk through a simple example.
First, these are the three chips that have the separate code we want to execute in order.
// Chip 0, Chipwait = Q0
:o = "First Chip"
// Chip 1, Chipwait = Q1
:o += "\nSecond Chip"
// Chip 2, Chipwait = Q2
:o += "\nThird Chip"
Then, we have a chip that handles starting the daisy chaining. Remember that a value of 0 means execute.
// Start Daisychain Chip
:Q3 = 0 goto1
And then we have a chip that resets the daisy chain. Remember that negative values mean pause execution.
// Reset Daisychain Chip, Chipwait = Q3
:Q3 = -1
// And finally we set up our Memory Relay values.
| Input Memory Field | Output Memory Field |
|---|---|
| Q3 | Q2 |
| Q2 | Q1 |
| Q1 | Q0 |
All together, you end up with this flow:
- Tick #1 starts.
-
Q3is set to -1, which will stop execution next tick. - Chips execute in order Q0 -> Q1 -> Q2 because we are popping off the stack.
- Memory Relay sets
Q3 => Q2. - Memory Relay sets
Q2 => Q1. - Memory Relay sets
Q1 => Q0. - Tick #1 ends. Tick #2 starts.
-
Q3is set to 0, which will start the daisy chain next tick. - Chips do not execute because their chipwaits are set to -1.
- Memory Relay sets
Q3 => Q2. Q2 gets pushed onto the execution stack. - Memory Relay sets
Q2 => Q1. Q1 gets pushed onto the execution stack. - Memory Relay sets
Q1 => Q0. Q0 gets pushed onto the execution stack. - Tick #2 ends. Loop back to start.
And that's how we can execute chips in a deterministic order.
With one of these, you are limited to executing 10 chips in order. But you can extend the daisychain to include more than 1 Memory Relay by having the second relay pick up where the last one left off. So if the first relay ended on Q10, then the second relay starts on Q10 and continues to Q20.
Unfortunately there are a few limitations with this method.
First, it takes 2 ticks to go through the whole execution loop. One tick is used for chip execution and the second tick is use to reset all chipwait values to -1 so it works right the next time around. As far as I am aware, there is no way around this. But I am curious about if there is a way to combine two of these systems to run in opposite states of each other, so while one daisy chain is resetting, the other is executing.
Secondly, having 2 chips simultaneously trying to set the chipwait value of another to -1 and 0 at the same time is normally fine when you have only one pair of them. But when you start to add more of these pairs, chip timings across the ship start to wig out. How much they wig out and whether or not there is a solution still requires some testing.