UMassProcessor::ProcessorRequirements and concurrent subsystem access.

Hello,

We’ve seen some unexpected behavior around mass processors with subsystem requirements, and wanted to clarify the intended use of UMassProcessor::ProcessorRequirements.

An example is a subsystem that we have set up for mass access, which is can be used concurrently with a writer/readers access pattern. To have that pattern, we have TMassExternalSubsystemTraits set up for it with both GameThreadOnly = false, and ThreadSafeWrite = false. We were seeing that despite this, we would have mass processors that were set to require it as ReadOnly were running concurrently with a processor set to access it as ReadWrite. It turns out that this was because at least one of those processors was setting the requirement on ProcessorRequirements, rather than any registered FMassEntityQuery.

The dependency solver seems to not currently reflect the access requirements in UMassProcessor::ProcessorRequirements. This is because FMassProcessorDependencySolver::CreateNodes calls UMassProcessor::ExportRequirements to set the processor node requirements, and UMassProcessor::ExportRequirements only iterates over the OwnedQueries to export requirements.

We’ve locally added exporting the ProcessorRequirements in addition to the requirements from the OwnedQueries, and with that change, are seeing the behavior we’d expect.

void UMassProcessor::ExportRequirements(FMassExecutionRequirements& OutRequirements) const
{
	for (FMassEntityQuery* Query : OwnedQueries)
	{
		CA_ASSUME(Query);
		Query->ExportRequirements(OutRequirements);
	}
 
	// Without these requirements, the dependency graph won't consider the subsystem requirements.
	ProcessorRequirements.ExportRequirements(OutRequirements);
}

Are we using UMassProcessor::ProcessorRequirements in an unintended way?

It looks like one possible use for it is that it is coupled with UMassProcessor::AutoExecuteQuery -- the requirements are set on that query if it is valid, which seems to allow the user to just set AutoExecuteQuery and ProcessorRequirements, rather than overriding ConfigureQueries and Execute. If that were the only intended purpose, then it would make sense to only use the requirements from the queries in UMassProcessor::ExportRequirements. This use case doesn’t actually seem to be done anywhere except for a single case in the test suite.

However, the much more common use case seems to be to set UMassProcessor::ProcessorRequirements to the requirements that the Execute method has outside of the queries that it runs. This was how we interpreted the intended use of this, and is the pattern we’ve followed. Given that pattern, it seems like our change to UMassProcessor::ExportRequirements is necessary, in order to set up the correct dependencies for the node. Is there something else that we’re missing, though?

Thanks.

It certainly seems like a bug as we do have places where we add subsystems to a processor’s ProcessorRequirements, and your solution makes sense. You are also correct for the purpose behind UMassProcessor::ProcessorRequirements being to add any requirements run outside of the queries. Multiple processors we have made list the subsystem required in ProcessorRequirements as the processor caches the subsystem before running any queries or even uses the subsystem after the queries have all finished executing.

-James