How to organize interactions between entities on a grid? (ecs/dots)

I have an entity representing a plant. The entity has some components such as Grow, Age, GridCoord, Spread. When the Spread component timer reaches a threshold the plant should spread to nearby cells. The cells are 32 x 32 entities with their own logic.

How can I communicate between the Spread component and the cell at the Spread coordinate to mark that cell as “occupied” so that no more than one plant can occupy a single cell?

I have tried keeping a NativeArray of a component on the cells and changing that NativeArray at the correct index but that doesn’t work as the entity connected to that component doesn’t update.

[196839-gif-28052022-13-46-06v2.gif*_|196839]

// src: https://gist.github.com/andrew-raphael-lukasik/19170443b976582f97404f6da9875b4c
using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;

namespace Sandbox
{
	public class Planted : MonoBehaviour
	{
		[SerializeField] int _numPlantsOnStart = 5;
		[SerializeField] int2 _coordMinOnStart = -10, _coordMaxnOnStart = 10;
		EntityManager _entityManager;
		EntityQuery _query;

		void Start ()
		{
			var world = World.DefaultGameObjectInjectionWorld;
			_entityManager = world.EntityManager;
			_query = _entityManager.CreateEntityQuery( ComponentType.ReadOnly<Coord>() , ComponentType.ReadOnly<Spread>() , ComponentType.ReadOnly<Gene>() );
			var prefabs = world.GetOrCreateSystem<PrefabSystem>();
			var rnd = new Unity.Mathematics.Random( (uint) System.DateTime.Now.GetHashCode() );
			for( int i=0 ; i<_numPlantsOnStart ; i++ )
			{
				Entity plantInstance = _entityManager.Instantiate( prefabs.Plant );
				_entityManager.SetComponentData( plantInstance , new Coord{
					Value = rnd.NextInt2( _coordMinOnStart , _coordMaxnOnStart )
				} );
				_entityManager.SetComponentData( plantInstance , new Age{
					Value = rnd.NextFloat( 0f , rnd.NextFloat(Age.limit*0.1f) )
				} );

				var genes = _entityManager.GetBuffer<Gene>( plantInstance );
				Color32 colorBytes = Color.HSVToRGB( rnd.NextFloat(1f) , 1f , 1f );
				genes.Add( colorBytes.r );
				genes.Add( colorBytes.g );
				genes.Add( colorBytes.b );
			}
		}
		#if UNITY_EDITOR
		void OnDrawGizmos ()
		{
			if( !Application.isPlaying ) return;
			
			int len = _query.CalculateEntityCount();
			var entities = _query.ToEntityArray(Allocator.Temp).ToArray();
			var coords = _query.ToComponentDataArray<Coord>(Allocator.Temp).ToArray();
			for( int i=0 ; i<len ; i++ )
			{
				var genes = _entityManager.GetBuffer<Gene>( entities *);*
  •  		Gizmos.color = new Color32( genes[0] , genes[1] , genes[2] , 255 );*
    

_ int2 coord = coords*.Value;_
_
Gizmos.DrawSphere( new Vector3( coord.x , coord.y , 0 ) , 0.5f );_
_
}_
_
}_
_
#endif*_
* }*

* public struct Coord : IComponentData*
* {*
* public int2 Value;*
* }*
* public struct Age : IComponentData*
* {*
* public float Value;*
* public const float limit = 10f;*
* }*
* public struct Spread : IComponentData*
* {*
* public float Value;*
* public const float spread_threshold = 2f;
_
}_
_
[InternalBufferCapacity(5)]_
_
public struct Gene : IBufferElementData*_
* {*
* public byte Value;*
* public static implicit operator byte ( Gene value ) => value.Value;*
* public static implicit operator Gene ( byte value ) => new Gene{ Value=value };*
* }*

* [UpdateInGroup( typeof(SimulationSystemGroup) )]*
* public partial class SpreadSystem : SystemBase*
* {*
* EntityCommandBufferSystem _ecbSystem;
GridSystem _gridSystem;
PrefabSystem prefabs;
_
protected override void OnCreate ()*

* {*
* base.OnCreate();*
* _ecbSystem = World.GetOrCreateSystem();
_gridSystem = World.GetOrCreateSystem();
prefabs = World.GetOrCreateSystem();
_
}*

* protected override void OnUpdate ()*
* {*
* float deltaTime = Time.DeltaTime;*
* uint rndHash = (uint) Time.ElapsedTime.GetHashCode();*
* var grid = _gridSystem.Lookup;
var plantPrefab = prefabs.Plant;
_
var geneBuffers = GetBufferFromEntity( isReadOnly:false );*

* var cmd = _ecbSystem.CreateCommandBuffer().AsParallelWriter();*

* this.Dependency = JobHandle.CombineDependencies( Dependency , _gridSystem.LookupDependency );*

* Entities*
* .WithReadOnly( grid )*
* .WithAll()*
* .ForEach( ( ref Spread spread , in Coord coord , in Entity e , in int nativeThreadIndex ) =>*
* {*
* spread.Value += deltaTime;*

* if( spread.Value>Spread.spread_threshold )
_
{_
_
var rnd = new Unity.Mathematics.Random( rndHash + (uint)e.GetHashCode() + (uint)nativeThreadIndex.GetHashCode() );_
_
var myGenes = geneBuffers[e];*_

* int2 spreadAttemptCoord = coord.Value + rnd.NextInt2( -2 , 2 );*
* if( !grid.ContainsKey(spreadAttemptCoord) )// is this spot free*
* {*
* var newPlant = cmd.Instantiate( nativeThreadIndex , plantPrefab );*
* cmd.SetComponent( nativeThreadIndex , newPlant , new Coord{ Value=spreadAttemptCoord } );*
cmd.SetComponent( nativeThreadIndex , newPlant , new Spread{ Value=rnd.NextFloat(0f,Spread.spread_threshold0.5f) } );
_ cmd.SetComponent( nativeThreadIndex , newPlant , new Age{ Value=rnd.NextFloat(0f,Age.limit0.1f) } );
* // propagate genes with slight mutation:*
* const byte mutation_range_half = 30;
cmd.AppendToBuffer( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[0] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
cmd.AppendToBuffer( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[1] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
cmd.AppendToBuffer( nativeThreadIndex , newPlant , (byte)math.clamp( myGenes[2] + rnd.NextInt(-mutation_range_half,mutation_range_half+1) , 0 , 255 ) );
_
}*

* else// it’s taken so lets attempt cross-pollination*
* {*
* foreach( var other in grid.GetValuesForKey(spreadAttemptCoord) )*
* if( geneBuffers.HasComponent(other) )*
* {*
* var otherGenes = geneBuffers[other];*
* otherGenes[0] = rnd.NextBool() ? otherGenes[0] : myGenes[0];*
* otherGenes[1] = rnd.NextBool() ? otherGenes[1] : myGenes[1];*
* otherGenes[2] = rnd.NextBool() ? otherGenes[2] : myGenes[2];*
* }*
* }*

* spread.Value = 0;*
* }*
* } )*
* .WithBurst()*
* .Schedule();*

* ecbSystem.AddJobHandleForProducer( Dependency );
_
}*

* }*

* [UpdateInGroup( typeof(SimulationSystemGroup) )]*
* public partial class AgeSystem : SystemBase*
* {*
* EntityCommandBufferSystem ecbSystem;
_
protected override void OnCreate ()*

* {*
* base.OnCreate();*
* ecbSystem = World.GetOrCreateSystem();
_
}*

* protected override void OnUpdate ()*
* {*
* float deltaTime = Time.DeltaTime;*
* var cmd = _ecbSystem.CreateCommandBuffer().AsParallelWriter();*

* Entities*
* .ForEach( ( ref Age age , in Entity e , in int nativeThreadIndex ) =>*
* {*
* age.Value += deltaTime;*

* if( age.Value>Age.limit )*
* cmd.DestroyEntity( nativeThreadIndex , e );*
* } )*
* .WithBurst()*
* .ScheduleParallel();*

* ecbSystem.AddJobHandleForProducer( Dependency );
_
}*

* }*

* [UpdateInGroup( typeof(SimulationSystemGroup) , OrderFirst=true )]*
* public partial class GridSystem : SystemBase*
* {*
* ///

Read-Only *
* public NativeMultiHashMap<int2,Entity> Lookup { get; private set; }*
* public JobHandle LookupDependency => base.Dependency;*
* EntityCommandBufferSystem _ecbSystem;
EntityQuery queryNew;
_
protected override void OnCreate ()*

* {*
* base.OnCreate();*
* _ecbSystem = World.GetOrCreateSystem();
queryNew = EntityManager.CreateEntityQuery( new EntityQueryDesc{
_
None = new ComponentType[]{ ComponentType.ReadOnly() } ,*

* All = new ComponentType[]{ ComponentType.ReadOnly() }*
* } );*
* Lookup = new NativeMultiHashMap<int2,Entity>( 1024 , Allocator.Persistent );*
* }*
* protected override void OnDestroy ()*
* {*
* base.OnDestroy();*
* Lookup.Dispose();*
* }*
* protected override void OnUpdate ()*
* {*
* var cmd = ecbSystem.CreateCommandBuffer().AsParallelWriter();
_
var lookup = Lookup;*

* var lookupPW = Lookup.AsParallelWriter();*

* int countNewToLookup = queryNew.CalculateEntityCount();
_
if( countNewToLookup!=0 && (lookup.Count()+countNewToLookup)>lookup.Capacity/2 )*

* {*
_ lookup.Capacity = Mathf.Max( lookup.Count()+countNewToLookup , lookup.Capacity2 );_
_
Debug.Log($“{nameof(GridSystem)}.{nameof(Lookup)} capacity increased to {lookup.Capacity}”);_
_
}*_

* Entities*
* .WithName(“coord_created_job”)
_
.WithNone()_
_
.ForEach( ( in Coord coord , in Entity e , in int nativeThreadIndex ) =>_
_
{_
_
lookupPW.Add( coord.Value , e );_
_
cmd.AddComponent( nativeThreadIndex , e , new Tracked{ Value=coord.Value } );_
_
} )_
_
.WithBurst()_
_
.ScheduleParallel();*_

* Entities*
* .WithName(“coord_destroyed_job”)
_
.WithNone()_
_
.ForEach( ( in Tracked tracked , in Entity e , in int nativeThreadIndex ) =>_
_
{_
_
lookup.Remove( tracked.Value , e );_
_
cmd.RemoveComponent( nativeThreadIndex , e );_
_
} )_
_
.WithBurst()_
_
.Schedule();*_

* ecbSystem.AddJobHandleForProducer( Dependency );
_
}*

* struct Tracked : ISystemStateComponentData { public int2 Value; }*
* }*

* [DisableAutoCreation]*
* [UpdateInGroup( typeof(InitializationSystemGroup) )]*
* public partial class PrefabSystem : SystemBase*
* {*
* public Entity Plant { get; private set; }*
* protected override void OnCreate ()*
* {*
* base.OnCreate();*
* this.Enabled = false;*
* Plant = EntityManager.CreateEntity( typeof(Prefab) , typeof(Coord) , typeof(Age) , typeof(Spread) , typeof(Gene) );*
* }*
* protected override void OnUpdate () {}*
* }*

}
_*