I was curious if there were any ‘proper’ examples as well as perchance documentation for the intended use case of the IParallelForFilter.
I played with this some months back (this is still 2018.3, so old API) and leave this here…maybe it helps.
old test
namespace ParallelForFilterTest
{
public class JobFilter : MonoBehaviour
{
// Use this for initialization
void Start()
{
NativeList<int> indices = new NativeList<int>(Allocator.TempJob);
NativeArray<int> numbers = new NativeArray<int>(5, Allocator.TempJob);
numbers[0] = 0;
numbers[1] = 1;
numbers[2] = 0;
numbers[3] = 1;
numbers[4] = 0;
var handle = default(JobHandle);
/*
var condition = new Condition
{
indices = indices,
numbers = numbers
};
handle = condition.Schedule(handle);
*/
var append = new Append
{
numbers = numbers
};
handle = append.ScheduleAppend(indices, numbers.Length, 1, handle);
var filter = new Filter
{
numbers = numbers,
};
handle = filter.ScheduleFilter(indices, 1, handle);
handle.Complete();
Debug.Log(indices.Length);
indices.Dispose();
numbers.Dispose();
}
private struct Condition : IJob
{
public NativeList<int> indices;
public NativeArray<int> numbers;
public void Execute()
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == 1)
{
indices.Add(i);
}
}
}
}
// the first filter job must be scheduled with ScheduleAppend to create the initial NativeList containing the filtered indices to be iterated through (not required, if the Native List Already Exists)
private struct Append : IJobParallelForFilter
{
public NativeArray<int> numbers;
public bool Execute(int index)
{
return numbers[index] == 1;
}
}
// can chain a few of these for further filtering
private struct Filter : IJobParallelForFilter
{
public NativeArray<int> numbers;
public bool Execute(int index)
{
Debug.Log(index);
//return true;
return (index == 1);
}
}
}
}
This helps partially, but not so much for using it in the job system, as your example has cleanup handled via the start callback.
I’m trying to determine how to dispose/deallocate a native list (the indices) after the job is completed with it. The Attribute doesn’t apply to native lists, so the only method I have found is completing the job manually and dispose the list myself.
Since you can’t dispose of native lists from jobs the simplest solution is to keep a reference to your list in the system and do
if (nativeList_.IsCreated)
nativeList_.Dispose();
At the start of OnUpdate to clean it up next frame and in OnDestroy to clean it up when the system is destroyed.
Requires a complete on the next frame, which I don’t mind doing, but that makes for a lot of boiler plate. Was just ensuring that there wasn’t a better way from within the job. I assume no.
I am not entirely sure, what exactly you question is. So the example helped understanding the usage and now you want to know how to deallocate the list?
You could just allocate the list permanently in on create and dispose on destroy.
Then you have to clear it before scheduling the job
Use
[DeallocateOnJobCompletion]
That doesn’t work on NativeList
Granted whilst this is true, the example you provided was in non ECS oriented land. Where the job completion is all in your domain. This isn’t ideal in some cases. Your example helped, but the issue that arose was the list can not be deallocated via the attribute, which creates a sync point every time you want to use this filter method which is not ideal
What I am after is a filter that would be relatively smooth without forcing main thread to block for more than it has to.
Just a heads up you should probably put
if (nativeList_.IsCreated)
nativeList_.Dispose();
in a separate Monobehaviour’s FixedUpdate/Update. Use the singleton pattern to assign the NativeContainers of the Monobehaviour from inside a System. The reason for this is that a System may not run next couple of frames if you remove a required component for it to run during the previous frame. With a Monobehaviour the check for disposal always runs, albeit with some overhead. Here’s what I did
pathfindingDataHandle = GetExposedHelper.ProcessAll(
pathfindingDataHandle,
pathfindingHeightGrid,
pathfindingMapGrid,
pathfindingGridLength,
//FlankDisposer.GetInstance().pathfinderGridIndices,
FlankDisposer.GetInstance().enemiesVantages.AsDeferredJobArray(),
enemiesLength,
collisionWorld,
out var pathfindingExposedGrid,
out var pathfindingExposedGridLength
);
The FlankDisposer:
public class FlankDisposer : MonoBehaviour
{
private static FlankDisposer instance;
// destination
public NativeList<int> destinationMapMissesIndices;
public NativeList<int> destinationHeightsMissesIndices;
public NativeList<int> coverDisparityMissesIndices;
public NativeList<int3> enemiesVantages;
public NativeList<int> destinationCandidateIndices;
public NativeList<DestinationCandidate> destinationCandidateSuccesses;
public NativeList<int2> destinationCandidateSuccessesLocations;
public NativeList<int> destinationGridIndices;
// pathfinder
public NativeList<int> pathfinderGridIndices;
public NativeList<int2> pathfindingResults;
public NativeList<int> pathfindingResultsLengths;
public NativeList<byte> pathfindingSuccesses;
public NativeList<int2> path;
public static FlankDisposer GetInstance()
{
return instance;
}
public void Release()
{
if (destinationMapMissesIndices.IsCreated)
{
destinationMapMissesIndices.Dispose();
}
if (destinationHeightsMissesIndices.IsCreated)
{
destinationHeightsMissesIndices.Dispose();
}
if (enemiesVantages.IsCreated)
{
enemiesVantages.Dispose();
}
if (coverDisparityMissesIndices.IsCreated)
{
coverDisparityMissesIndices.Dispose();
}
if (destinationCandidateIndices.IsCreated)
{
destinationCandidateIndices.Dispose();
}
if (destinationCandidateSuccesses.IsCreated)
{
destinationCandidateSuccesses.Dispose();
}
if (destinationCandidateSuccessesLocations.IsCreated)
{
destinationCandidateSuccessesLocations.Dispose();
}
if (destinationGridIndices.IsCreated)
{
destinationGridIndices.Dispose();
}
if (pathfinderGridIndices.IsCreated)
{
pathfinderGridIndices.Dispose();
}
if (pathfindingResults.IsCreated)
{
pathfindingResults.Dispose();
}
if (pathfindingSuccesses.IsCreated)
{
pathfindingSuccesses.Dispose();
}
if (pathfindingResultsLengths.IsCreated)
{
pathfindingResultsLengths.Dispose();
}
if (path.IsCreated)
{
path.Dispose();
}
}
private void Awake()
{
instance = this;
}
}
Have a x-Disposer for each System, and have a single Monobehaviour Disposer call every x-Disposer’s Release method during its FixedUpdate/Update as well as OnDestroy methods.
I do not understand you problem (the sync point), could you paste some code?
If you have a long running job, you can check if it is completed, then call complete & dispose + schedule a new job. I would likely just allocate permanently as I mentioned earlier and .Clear() the list before scheduling the job
Perfect! Thanks buddy for pointing out that change
Finally.