Every now and then when you run a script in Space Engineers, you’ll see the dreaded “Script is too complex” error. The game unfortunately doesn’t give you any information as to what “too complex” means, or how you can fix your script.
What is ‘too complex’?
When Space Engineers compiles your code it instruments it in such a way that it can count the
number of operations you’re performing, as well as the number of method calls. For each operation or
method call a method in the VRage engine gets invoked and it increments and checks a counter. If
the counter is over a limit, then it throws a
ScriptOutOfRangeException which bubbles up out of
your script. The game UI then has special handling that detects that particular exception and
shows a custom error message:
Script execution terminated, script is too complex. Please edit and rebuilt script.
The actual limits the engine imposes are:
- Method calls: 10,000
- Operations: 50,000
That seems like quite a lot, but it’s easy to hit the operations limit if your script is running on a large grid and you’re not careful.
The operation count applies only to operations in your assembly. Calling an API method is one
operation, regardless of how complicated it is. This can have some important consequences when
you’re interacting with
Assume you have a grid that has 2,500 terminal blocks, 500 of which are lights. 75 blocks have “warning” in their name, of which 50 are lights.
If you want to select warning lights, you have a few ways of going about it. In terms of real performance, all of these are about the same.
var blocks = new List<IMyTerminalBlock>(); GridTerminalSystem.GetBlocks(blocks); for (var i = 0; i < blocks.Count; i++) if (blocks[i] is IMyInteriorLight && blocks[i].CustomName.Contains("Warning")) // ...
This uses the simplest API call, and it does all the filtering itself. Under-the-hood the Keen API is just applying the filters this way anyway, so it shouldn’t make much difference, right? In reality, this uses at least one operation per block, and two per light. So that’s around 3,500 operations gone. In a moderately complicated script that’s looking up multiple sets of blocks you’re probably going to start encountering problems.
var blocks = new List<IMyTerminalBlock>(); GridTerminalSystem.GetBlocksOfType<IMyInteriorLight>(blocks, block => block.CustomName.Contains("Warning")); // ...
This is a slightly more advanced use of the API. We’re telling it to filter down by type, and then passing in a custom function to do our name match. This looks like it only uses one or two operations, but every time your filter is called is counted as well. So in our example world you’re looking at 500 operations.
var blocks = new List<IMyTerminalBlock>(); GridTerminalSystem.SearchBlocksOfName("Warning", blocks, block => block is IMyInteriorLight); // ...
This final example flips the filtering around, having the game perform the name checking and passing in a lambda to do the type filtering. In the example world, this would be using more like 75 operations. That’s nearly 50x less than the first example, and around 7x less than the second.
Obviously these examples are specific to our made up block numbers but it’s usually a safe bet that filtering by names will be better than type, and filtering by type is better than nothing.
When you can’t possibly be less complex
If you’ve reduced the number of operations or methods as much as you can and you’re still getting ‘too complex’ errors, the standard workaround is to split the work up and do it in multiple passes.
For example, Taleden’s Inventory Manager has an option that lets you split the work over up to 9 steps, executed in turn. Each step performs a small amount of work so it flies under the operation count radar. The obvious downside is that for N steps, the whole function of the script takes N times longer to complete.