/**
         * Linear-time graph coloring using bitmasks and a look-up table. Used to organize contacts into batches for parallel processing.
         * input: array of unsorted constraints.
         * output:
         * - sorted constraints array.
         * - array of batchData, one per batch: startIndex, batchSize, workItemSize (at most == batchSize), numWorkItems
         * - number of active batches.
         */

        public JobHandle BatchConstraints <T>(NativeArray <T> contacts,
                                              int particleCount,
                                              ref NativeArray <T> sortedContacts,
                                              ref NativeArray <BatchData> batchData,
                                              ref NativeArray <int> activeBatchCount,
                                              JobHandle inputDeps) where T : struct, IConstraint
        {
            if (sortedContacts.Length != contacts.Length || activeBatchCount.Length != 1)
            {
                return(inputDeps);
            }

            var batchJob = new BatchContactsJob <T>()
            {
                batchMasks       = new NativeArray <ushort>(particleCount, Allocator.TempJob, NativeArrayOptions.ClearMemory),
                batchIndices     = new NativeArray <int>(contacts.Length, Allocator.TempJob, NativeArrayOptions.ClearMemory),
                lut              = batchLUT,
                contacts         = contacts,
                batchData        = batchData,
                sortedContacts   = sortedContacts,
                activeBatchCount = activeBatchCount,
                maxBatches       = maxBatches
            };

            return(batchJob.Schedule(inputDeps));
        }
        /**
         * Linear-time graph coloring using bitmasks and a look-up table. Used to organize contacts into batches for parallel processing.
         * input: array of unsorted constraints.
         * output:
         * - sorted constraint indices array.
         * - array of batchData, one per batch: startIndex, batchSize, workItemSize (at most == batchSize), numWorkItems
         * - number of active batches.
         */

        public JobHandle BatchConstraints <T>(ref T constraintDesc,
                                              int particleCount,
                                              ref NativeArray <BatchData> batchData,
                                              ref NativeArray <int> activeBatchCount,
                                              JobHandle inputDeps) where T : struct, IConstraintProvider
        {
            if (activeBatchCount.Length != 1)
            {
                return(inputDeps);
            }

            var batchJob = new BatchContactsJob <T>()
            {
                batchMasks       = new NativeArray <ushort>(particleCount, Allocator.TempJob, NativeArrayOptions.ClearMemory),
                batchIndices     = new NativeArray <int>(constraintDesc.GetConstraintCount(), Allocator.TempJob, NativeArrayOptions.ClearMemory),
                lut              = batchLUT,
                constraintDesc   = constraintDesc,
                batchData        = batchData,
                activeBatchCount = activeBatchCount,
                maxBatches       = maxBatches
            };

            return(batchJob.Schedule(inputDeps));
        }