I'm not sure what to do about this except to just catch such scenarios
Some other solution will be necessary for this. My normal approach is to make things "implicitly threadsafe" by either making race conditions impossible-in-principle (making sure that no sim-modifying thread is started during the short-term-planning threads that check for forcefield coverage or tractors or gravity or movement or targeting, for instance), or by making the race conditions irrelevant: either thread can win, and the result may vary, but I just don't care.
Since vis runs all the time, this generally means leaving data in a safe place for vis to find, and updating that data in an atomic way. By "atomic" I mean "nothing can happen between the start and end", and thus vis will either pick up the old data or the new data but will not get some inconsistent nonsense in the middle.
So in this case, I suggest for a first naive solution having two lists:
List<ConstructorData> ForSim_NanocaustConstructorData
List<ConstructorData> ForVis_NanocaustConstructorData
Do your work on the ForSim list
Then create a new list and copy ForSim into it
Then assign that new list to ForVis
And in the vis code you do:
List<ConstructorData> constructorDataListForThisCycle=ForVis;
and then only use constructorDataListForThisCycle (NOT ForVis) as your reference to the list in the vis code.
That way your vis code might get old data, but the list will be stable.
This has two major disadvantages:
- additional heap allocation; in general we want to avoid the new operator in stuff that runs frequently (I do it a lot but during performance passes I optimize lots of them out)
- it doesn't prevent race conditions with what's stored inside the ConstructorData objects, you have to practice vigilance at that level as well
But it has the advantage of being a simple and straightforward approach.
It may be that we get to a point with the race conditions where we make a major architectural shift to simply avoid them by not allowing access to the World_AIW2.Instance or other gamestate directly from external code anymore, but instead have the Context object parameter contain an interface that grants the appropriate level of access to the appropriate parts of the gamestate. That would be a lot safer in general, but for now I'm trying the Unrestricted Lumber Mill approach