void SetRange(int sectionIndex, int offset, int length, bool desiredFree) { if (sectionIndex < 0) { throw new ArgumentException($"{nameof(sectionIndex)} cannot be negative", nameof(sectionIndex)); } if (offset < 0) { throw new ArgumentException($"{nameof(offset)} cannot be negative", nameof(offset)); } if (length < 0) { throw new ArgumentException($"{nameof(length)} cannot be negative", nameof(length)); } if (length == 0) { return; } if (sectionIndex > sections.Length) { throw new ArgumentException($"{nameof(sectionIndex)} cannot be higher than number of sections", nameof(sectionIndex)); } // Check if we need to add a new one instead if (sectionIndex == sections.Length) { if (sectionIndex == 0 || // If sections is empty, just add a new section IsSectionFree(sectionIndex - 1) != desiredFree // If the previous section is different than desiredFree, we need to add a new section too ) { Debug.Assert(// either this is the first section and offset needs to be 0 (sectionIndex == 0 && offset == 0) || // or it connects to the previous section (sectionIndex > 0 && offset == sections[sectionIndex - 1].start + sections[sectionIndex - 1].length)); sections.Add(new Section { start = offset, length = length }); if (sectionIndex == 0) { firstElementFree = desiredFree; } Debug.Assert(IsSectionFree(sectionIndex) == desiredFree); } else { // Otherwise, we can merge our allocation with the previous allocated section ... Debug.Assert(IsSectionFree(sectionIndex - 1) == desiredFree); var previousSection = sections[sectionIndex - 1]; Debug.Assert(previousSection.start + previousSection.length == offset); previousSection.length += length; sections[sectionIndex - 1] = previousSection; } return; } if (IsSectionFree(sectionIndex) == desiredFree) { if (desiredFree) { throw new ArgumentException("Cannot free section because it's already free"); } else { throw new ArgumentException("Cannot allocate section because it's already allocated"); } } var section = sections[sectionIndex]; if (offset + length > section.start + section.length) { throw new IndexOutOfRangeException("Length of requested section to free is larger than found section"); } // Note: Free and allocated sections follow each other since they always get merged // with allocated or free sections next to them // Check if our section is exactly the free range we need if (section.start == offset && section.length == length) { // Check if our section is the last section in the list, which means there's no next section if (sectionIndex + 1 == sections.Length) { // If we're the first section, then there's no previous section if (sectionIndex == 0) { // This means this is the only section in the list and we can just modify it sections[sectionIndex] = section; if (sectionIndex == 0) { firstElementFree = desiredFree; } } else { // Otherwise, we can merge our allocation with the previous allocated section ... Debug.Assert(IsSectionFree(sectionIndex - 1) == desiredFree); var previousSection = sections[sectionIndex - 1]; Debug.Assert(previousSection.start + previousSection.length == section.start); previousSection.length += length; sections[sectionIndex - 1] = previousSection; // ... and remove the last item in the list (the found section) sections.RemoveAt(sectionIndex); } } else // We know that there's a next section. { // If we're the first section, then there's no previous section if (sectionIndex == 0) { // Merge our allocation with the next section ... Debug.Assert(IsSectionFree(sectionIndex + 1) == desiredFree); var nextSection = sections[sectionIndex + 1]; Debug.Assert(nextSection.start == section.start + section.length); nextSection.start -= length; nextSection.length += length; sections[sectionIndex + 1] = nextSection; // ... and remove the first item in the list (the found section) sections.RemoveAt(sectionIndex); firstElementFree = desiredFree; } else { // We have both a previous and a next section, and we can merge all // three sections together into one section Debug.Assert(IsSectionFree(sectionIndex - 1) == desiredFree); var previousSection = sections[sectionIndex - 1]; Debug.Assert(previousSection.start + previousSection.length == section.start); Debug.Assert(IsSectionFree(sectionIndex + 1) == desiredFree); var nextSection = sections[sectionIndex + 1]; Debug.Assert(nextSection.start == section.start + section.length); previousSection.length = previousSection.length + length + nextSection.length; sections[sectionIndex - 1] = previousSection; // ... and we remove the two entries we don't need anymore sections.RemoveRangeWithBeginEnd(sectionIndex, sectionIndex + 2); } } } else // If our allocation doesn't match the section exactly, we need to keep a leftover { var firstSectionLength = offset - section.start; Debug.Assert(firstSectionLength >= 0); var middleSectionLength = length; var lastSectionLength = (section.start + section.length) - (offset + length); Debug.Assert(lastSectionLength >= 0); if (firstSectionLength == 0) { Debug.Assert(lastSectionLength > 0); if (sectionIndex == 0) { // Modify the existing section to hold the middle section sections[sectionIndex] = new Section { start = offset, length = middleSectionLength }; // Insert a new section behind it to hold the leftover sections.InsertAt(sectionIndex + 1, new Section { start = offset + middleSectionLength, length = lastSectionLength }); firstElementFree = desiredFree; } else { // Modify the existing section to hold the left-over sections[sectionIndex] = new Section { start = offset + middleSectionLength, length = lastSectionLength }; // Merge middle section with the previous section Debug.Assert(IsSectionFree(sectionIndex - 1) == desiredFree); var previousSection = sections[sectionIndex - 1]; Debug.Assert(previousSection.start + previousSection.length == section.start); previousSection.length += middleSectionLength; sections[sectionIndex - 1] = previousSection; } } else if (lastSectionLength == 0) { Debug.Assert(firstSectionLength > 0); // Modify the existing section to hold the left-over sections[sectionIndex] = new Section { start = section.start, length = firstSectionLength }; if (sectionIndex == 0) { firstElementFree = !desiredFree; } if (sectionIndex + 1 == sections.Length) { // Insert a new section to hold the middle section sections.InsertAt(sectionIndex + 1, new Section { start = offset, length = middleSectionLength }); } else { // Merge middle section with the next section Debug.Assert(IsSectionFree(sectionIndex + 1) == desiredFree); var nextSection = sections[sectionIndex + 1]; Debug.Assert(nextSection.start == section.start + section.length); nextSection.start -= middleSectionLength; nextSection.length += middleSectionLength; sections[sectionIndex + 1] = nextSection; } } else { // Modify the existing section to hold the first left-over sections[sectionIndex] = new Section { start = section.start, length = firstSectionLength }; if (sectionIndex == 0) { firstElementFree = !desiredFree; } // Add the middle section sections.InsertAt(sectionIndex + 1, new Section { start = offset, length = middleSectionLength }); var lastSection = new Section { start = offset + middleSectionLength, length = lastSectionLength }; // Add the last left-over sections.InsertAt(sectionIndex + 2, lastSection); } } }