/// <summary> /// Indexer for access to the item cache /// </summary> /// <param name="index">Item Index</param> /// <returns></returns> public T this[int index] { get { // iterates through the cache blocks to find the item foreach (CacheEntryBlock <T> block in _cacheBlocks) { if (index >= block.FirstIndex && index <= block.LastIndex) { return(block.Items[index - block.FirstIndex]); } } return(default(T)); } set { // iterates through the cache blocks to find the right block for (int i = 0; i < _cacheBlocks.Count; i++) { CacheEntryBlock <T> block = _cacheBlocks[i]; if (index >= block.FirstIndex && index <= block.LastIndex) { block.Items[index - block.FirstIndex] = value; // register that we have the result in the cache if (value != null) { _cachedResults.Add((uint)index, 1); } return; } // We have moved past the block where the item is supposed to live if (block.FirstIndex > index) { AddOrExtendBlock(index, value, i); return; } } // No blocks exist, so creating a new block AddOrExtendBlock(index, value, _cacheBlocks.Count); } }
// Compares the new ranges against the previous ones to see if they have changed private bool HasRangesChanged(ItemIndexRange[] ranges) { if (ranges.Length != cacheBlocks.Count) { return(true); } for (int i = 0; i < ranges.Length; i++) { ItemIndexRange r = ranges[i]; CacheEntryBlock <T> block = this.cacheBlocks[i]; if (r.FirstIndex != block.FirstIndex || r.LastIndex != block.lastIndex) { return(true); } } return(false); }
// Extends an existing block if the item fits at the end, or creates a new block private void AddOrExtendBlock(int index, T value, int insertBeforeBlock) { if (insertBeforeBlock > 0) { CacheEntryBlock <T> block = cacheBlocks[insertBeforeBlock - 1]; if (block.lastIndex == index - 1) { T[] newItems = new T[block.Length + 1]; Array.Copy(block.Items, newItems, (int)block.Length); newItems[block.Length] = value; block.Length++; block.Items = newItems; return; } } CacheEntryBlock <T> newBlock = new CacheEntryBlock <T>() { FirstIndex = index, Length = 1, Items = new T[] { value } }; cacheBlocks.Insert(insertBeforeBlock, newBlock); }
/// <summary> /// Updates the desired item range of the cache, discarding items that are not needed, /// and figuring out which items need to be requested. It will then kick off a fetch if required. /// </summary> /// <param name="ranges">New set of ranges the cache should hold</param> public void UpdateRanges(ItemIndexRange[] ranges) { //Normalize ranges to get a unique set of discontinuous ranges ranges = NormalizeRanges(ranges); // Fail fast if the ranges haven't changed if (!HasRangesChanged(ranges)) { return; } //To make the cache update easier, we'll create a new set of CacheEntryBlocks List <CacheEntryBlock <T> > newCacheBlocks = new List <CacheEntryBlock <T> >(); foreach (ItemIndexRange range in ranges) { CacheEntryBlock <T> newBlock = new CacheEntryBlock <T>() { FirstIndex = range.FirstIndex, Length = range.Length, Items = new T[range.Length] }; newCacheBlocks.Add(newBlock); } #if TRACE_DATASOURCE string s = "┌ " + debugName + ".UpdateRanges: "; foreach (ItemIndexRange range in ranges) { s += range.FirstIndex + "->" + range.LastIndex + " "; } Debug.WriteLine(s); #endif //Copy over data to the new cache blocks from the old ones where there is overlap int lastTransferred = 0; for (int i = 0; i < ranges.Length; i++) { CacheEntryBlock <T> newBlock = newCacheBlocks[i]; ItemIndexRange range = ranges[i]; int j = lastTransferred; while (j < this.cacheBlocks.Count && this.cacheBlocks[j].FirstIndex <= ranges[i].LastIndex) { ItemIndexRange overlap, oldEntryRange; ItemIndexRange[] added, removed; CacheEntryBlock <T> oldBlock = this.cacheBlocks[j]; oldEntryRange = new ItemIndexRange(oldBlock.FirstIndex, oldBlock.Length); bool hasOverlap = oldEntryRange.DiffRanges(range, out overlap, out removed, out added); if (hasOverlap) { Array.Copy(oldBlock.Items, overlap.FirstIndex - oldBlock.FirstIndex, newBlock.Items, overlap.FirstIndex - range.FirstIndex, (int)overlap.Length); #if TRACE_DATASOURCE Debug.WriteLine("│ Transfering cache items " + overlap.FirstIndex + "->" + overlap.LastIndex); #endif } j++; if (ranges.Length > i + 1 && oldBlock.lastIndex < ranges[i + 1].FirstIndex) { lastTransferred = j; } } } //swap over to the new cache this.cacheBlocks = newCacheBlocks; //figure out what items need to be fetched because we don't have them in the cache this.requests = new ItemIndexRangeList(ranges); ItemIndexRangeList newCachedResults = new ItemIndexRangeList(); // Use the previous knowlege of what we have cached to form the new list foreach (ItemIndexRange range in ranges) { foreach (ItemIndexRange cached in this.cachedResults) { ItemIndexRange overlap; ItemIndexRange[] added, removed; bool hasOverlap = cached.DiffRanges(range, out overlap, out removed, out added); if (hasOverlap) { newCachedResults.Add(overlap); } } } // remove the data we know we have cached from the results foreach (ItemIndexRange range in newCachedResults) { this.requests.Subtract(range); } this.cachedResults = newCachedResults; startFetchData(); #if TRACE_DATASOURCE s = "└ Pending requests: "; foreach (ItemIndexRange range in this.requests) { s += range.FirstIndex + "->" + range.LastIndex + " "; } Debug.WriteLine(s); #endif }