Vixen
dd8b0a81
csharp
public sealed class BehaviorJobAnalyzer

Reports work inside a [BehaviorJob] batch that several threads cannot do at once.

No guide page documents this yet — the page shows what the code says about itself.

Remarks

The half that makes the dispatch safe. docs/plan/04-ecs-and-scripting.md § Making it fast, item 3 asks for behaviour batches dispatched across the job system. What that section spends its length on is the blocker: a behaviour declares no access set, and — more immediately — its own convenience API reaches state the whole store shares. Enabled queues into a plain List<Behavior?>, Destroy() and Run(coroutine) do the same to their own queues, and none of it is synchronised, deliberately, because the lifecycle is drained on one thread. Inside a dispatched Update that is a data race rather than a slow path.

⚠ And the least visible member of the set is Get<T> of a managed component. Nothing at the call site says so: the world resolves a managed cell through StoreFor<T>(), which can resize the world's table and allocate a row's slot, so two behaviours reading the same managed component for the first time race each other. The read half of that was fixed — Read<T> and TryGet<T> resolve without writing — which is why this rule names Get<T> and not Read<T>: what is left is a write to world-wide state wearing a getter's face.

What it reads is the two bodies that are dispatched — the marked type's Update and LateUpdate, and the lambdas and local functions inside them — and nothing further down. ⚠ That bound is stated because it is the same one VXHP0001 states about itself: a helper method two calls away can queue a lifecycle change and this will not see it. Widening it to every method of a marked type would be worse than the gap, because Awake and OnEnable run on the drain thread where every one of these calls is correct.

An error, not a warning, and unlike its siblings it has no #pragma story. The escape from this rule is to take [BehaviorJob] off the type, which costs a batch its parallelism and nothing else. A suppressed race is a build that runs ten thousand instances across eight threads into an unsynchronised list.

Fields and properties (3)

  • public const string RaceDiagnosticId

    The id reported for shared state touched inside a dispatched batch.

  • public const string TargetDiagnosticId

    The id reported for [BehaviorJob] on a type no bucket will ever hold.

  • public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics

    Returns a set of descriptors for the diagnostics that this analyzer is capable of producing.

Methods (1)

  • public override void Initialize(AnalysisContext context)

    Called once at session start to register actions in the analysis context.

Used by (1)

  • BehaviorJobRefusalTestsVixen.Engine.Generators.Tests