/// <summary> /// This approach uses a single large NativeArray of samples filled in by each job instead of adding temporary DynamicBuffers to the entities. /// This appears to be the best balance between speed and simplicity. /// /// Test results: /// Total: ~40ms /// Setup: ~0ms /// Combine: 2.27ms /// </summary> private List <List <IntPoint> > PolygonsFromRoadOutlineEntityForEachSeparateBuffer(float extensionDistance, JobHandle jobHandle) { Profiler.BeginSample("PolygonsFromRoadOutlineEntityForEachSeparateBuffer"); Profiler.BeginSample("PolygonsFromRoadOutlineEntityForEachSeparateBuffer_Setup"); var polygons = new List <List <IntPoint> >(); var sampleStartIndexMap = new NativeHashMap <Entity, RoadSampleSpan>(300, Allocator.TempJob); var sampleCountTotal = 0; Entities.ForEach((int entityInQueryIndex, Entity entity, ref EcsRoad road) => { var sampleCount = GeometrySampling.ComputeSampleCountForRoadOutline(road, .5f); sampleStartIndexMap[entity] = new RoadSampleSpan { startIndex = sampleCountTotal, count = sampleCount }; sampleCountTotal += sampleCount; }).Run(); var samples = new NativeArray <PointSampleGlobal>(sampleCountTotal, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var job = Entities.ForEach((Entity entity, ref EcsRoad road, in DynamicBuffer <Geometry> ecsGeometries, in DynamicBuffer <EcsLane> ecsLanes, in DynamicBuffer <EcsLaneSection> ecsLaneSections, in DynamicBuffer <LaneOffset> laneOffsets, in DynamicBuffer <LaneWidthRecord> laneWidthRecords) => //(Entity entity, ref EcsRoad road) => { var ecsRoadData = new EcsRoadData() { ecsRoad = road, ecsGeometries = ecsGeometries, ecsLaneSections = ecsLaneSections, ecsLanes = ecsLanes, laneOffsets = laneOffsets, laneWidthRecords = laneWidthRecords };; var roadSampleSpan = sampleStartIndexMap[entity]; GeometrySampling.BuildSamplesFromRoadOutlineWithExtensionDistance( ecsRoadData, 0.5f, extensionDistance, new NativeSlice <PointSampleGlobal>(samples, roadSampleSpan.startIndex, roadSampleSpan.count)); }).WithReadOnly(sampleStartIndexMap).WithoutBurst().Schedule(jobHandle);
/// <summary> /// This approach extends the idea in Job.WithCode() by manually generating batches of work with one job per batch for better load balancing. /// Setup is tedious, but is slightly faster (~1-2ms) than Entities.ForEach with a separate buffer. We should probably avoid this pattern due to the large overhead and brittle code. /// /// Test results: /// Total: 36ms /// Setup: 1.12ms /// Combine: 2.12ms /// </summary> private List <List <IntPoint> > PolygonsFromRoadOutlineIJobParallelFor(float extensionDistance, JobHandle jobHandle) { Profiler.BeginSample("PolygonsFromRoadOutlineIJobParallelFor"); Profiler.BeginSample("PolygonsFromRoadOutlineIJobParallelFor_Setup"); var polygons = new List <List <IntPoint> >(); var outlineJobParams = new NativeList <OutlineJobParams>(300, Allocator.TempJob); var sampleCountTotal = 0; Entities.ForEach((int entityInQueryIndex, Entity entity, ref EcsRoad road) => { var sampleCount = GeometrySampling.ComputeSampleCountForRoadOutline(road, .5f); outlineJobParams.Add(new OutlineJobParams { entity = entity, startIndex = sampleCountTotal, count = sampleCount }); sampleCountTotal += sampleCount; }).Run(); var samples = new NativeArray <PointSampleGlobal>(sampleCountTotal, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var ecsRoadGetter = GetComponentDataFromEntity <EcsRoad>(true); var geometryGetter = GetBufferFromEntity <Geometry>(true); var laneSectionGetter = GetBufferFromEntity <EcsLaneSection>(true); var laneGetter = GetBufferFromEntity <EcsLane>(true); var laneOffsetGetter = GetBufferFromEntity <LaneOffset>(true); var laneWidthRecordGetter = GetBufferFromEntity <LaneWidthRecord>(true); var batchSize = samples.Length / 64; var jobBatches = new NativeList <JobHandle>(Allocator.TempJob); int currentBatchSize = 0; int currentBatchStartIndex = 0; for (int i = 0; i < outlineJobParams.Length; i++) { var currentJobParams = outlineJobParams[i]; currentBatchSize += currentJobParams.count; if (currentBatchSize >= batchSize || i == outlineJobParams.Length - 1) { var job = new BuildSamplesJob() { extensionDistance = extensionDistance, outlineJobBatch = new OutlineJobBatch { startIndex = currentBatchStartIndex, count = i - currentBatchStartIndex + 1 }, outlineJobParams = outlineJobParams, ecsRoadGetter = ecsRoadGetter, geometryGetter = geometryGetter, laneSectionGetter = laneSectionGetter, laneGetter = laneGetter, laneOffsetGetter = laneOffsetGetter, laneWidthRecordGetter = laneWidthRecordGetter, samples = samples }.Schedule(jobHandle); jobBatches.Add(job); currentBatchSize = 0; currentBatchStartIndex = i + 1; } } Profiler.EndSample(); Profiler.BeginSample("PolygonsFromRoadOutlineIJobParallelFor_CompleteJob"); JobHandle.CombineDependencies(jobBatches).Complete(); Profiler.EndSample(); Profiler.BeginSample("PolygonsFromRoadOutlineIJobParallelFor_Combine"); foreach (var outlineJobParam in outlineJobParams) { var sampleSlice = new NativeSlice <PointSampleGlobal>(samples, outlineJobParam.startIndex, outlineJobParam.count); var path = new List <IntPoint>(sampleSlice.Length); foreach (var point in sampleSlice) { path.Add(new IntPoint( point.pose.pos.x * PlacementUtility.UpScaleFactor, point.pose.pos.z * PlacementUtility.UpScaleFactor)); } if (!Clipper.Orientation(path)) { path.Reverse(); } polygons.Add(path); } outlineJobParams.Dispose(); jobBatches.Dispose(); samples.Dispose(); Profiler.EndSample(); Profiler.EndSample(); return(polygons); }