Major UpdateI've finally completed the transition to StateSnapshot. This has resulted in a major overhaul of pretty much every part of the mission SDK. Some might call it DotNetMissionSDK version 2.0
.
State SnapshotI needed an immutable, threadsafe state that could be used for AI async processing. Using the live Outpost2 state was not going to cut it.
Behold, StateSnapshot. This is the cornerstone of this update.
StateSnapshot is an immutable class object that is generated at the beginning of every frame. This object contains a wealth of read-only OP2 state information including easy to access player/gaia data, unit lists, unit info, and generated maps.
Units and UnitInfo have been broken into separate derived classes so that it belongs to its correct type. For example, you cannot access objectOnPad from a convec, you must use the SpaceportState object.
The snapshot never changes and exists until garbage collected, which is perfect for performing async operations over time without locks and while maintaining predictability.
Accessing data looks like this:
UniversityState university = stateSnapshot.players[0].units.universities[0];
if (stateSnapshot.players[0].commandMap.ConnectsTo(university.GetRect()))
// university connects to a command center, do something
Game StateThe static GameState class is the new access point for live data. The game state contains lookup tables for players and units. The idea is to use StateSnapshot to read data and perform calculations, and then access the live data with GameState when ready to issue commands and modify data.
Accessing and modifying data looks like this:
UniversityState university = stateSnapshot.players[0].units.universities[0];
if (stateSnapshot.players[0].commandMap.ConnectsTo(university.GetRect()))
GameState.GetUnit(university.unitID).DoTrainScientists(Math.Min(stateSnapshot.players[0].workers, 10));
Async PumpThe static AsyncPump class allows for predictable multithreading.
When you want to run an async task, you call AsyncPump.Run from the main thread and pass the amount of TethysGame.Time() you want to wait to complete execution. The first callback parameter runs asynchronously, the second one is called from the main thread at the time requested. If your task finishes early, it waits for the target time. If the time is reached before the task finishes, the main thread will wait for it to complete.
Combined with StateSnapshot, you can achieve a predictable game state every time.
Accessing and modifying data asynchronously looks like this:
ThreadAssert.MainThreadRequired();
if (m_IsProcessing)
return;
m_IsProcessing = true;
AsyncPump.Run(() =>
{
List<Action> actions = new List<Action>();
// Do async tasks
UniversityState university = stateSnapshot.players[0].units.universities[0];
if (stateSnapshot.players[0].commandMap.ConnectsTo(university.GetRect()))
{
// Add command to list for processing on main thread.
// The ? in the command checks if unit is null before executing DoTrainScientists.
// GameState.GetUnit will return null if the unit has been destroyed.
// This may happen due to the Time() difference between the snapshot time and when the command is finally executed.
actions.Add(() => GameState.GetUnit(university.unitID)?.DoTrainScientists(Math.Min(stateSnapshot.players[0].workers, 10)));
}
return actions;
},
(object returnState) =>
{
m_IsProcessing = false;
// Execute all completed actions
List<Action> actions = (List<Action>)returnState;
foreach (Action action in actions)
action();
});
Thread AssertThreadAssert.MainThreadRequired() is used on various methods to prevent access from async tasks. This helps prevent desync and access violation related issues from multithreading. In other words, these methods are not "thread-safe".
AI PerformanceWith the new systems above, AI now has a lot of wiggle room when it comes to performance.
First, AI no longer has to perform a complete update on every frame, instead performing its updates over several frames/TethysGame.Time().
Second, AI is now almost entirely performed on separate threads. Each AI manager performs an async update, putting all of its unit commands into a list, which is then executed on the main thread's completion callback.
Upcoming- Fixing some AI regression from the new changes, including activating async mode on BaseManager.
- Finishing ThreadAssert for OP2 API.
- Breaking up the "god object" UnitEx class.
- Tons of other AI related tasks from before.
Will post a video of the AI soon for the lazy.