sources/Google.Solutions.Mvvm/Cache/ModelCachingViewModel.cs (78 lines of code) (raw):
//
// Copyright 2020 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Common;
using Google.Solutions.Common.Diagnostics;
using Google.Solutions.Common.Util;
using Google.Solutions.Mvvm.Binding;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Google.Solutions.Mvvm.Cache
{
/// <summary>
/// View model that maintains an LRU cache of models.
/// </summary>
public abstract class ModelCachingViewModelBase<TModelKey, TModel> : ViewModelBase
where TModel : class
where TModelKey : class
{
private readonly LeastRecentlyUsedCache<TModelKey, TModel> modelCache;
private CancellationTokenSource? tokenSourceForCurrentTask = null;
/// <summary>
/// Current model.
/// </summary>
protected TModel? Model { get; private set; }
/// <summary>
/// Current key.
/// </summary>
protected TModelKey? ModelKey { get; private set; }
protected ModelCachingViewModelBase(int cacheCapacity)
{
this.modelCache = new LeastRecentlyUsedCache<TModelKey, TModel>(
cacheCapacity);
}
public async Task SwitchToModelAsync(TModelKey key)
{
if (this.Model != null)
{
//
// Reset.
//
this.Model = default;
ApplyModel(false);
}
this.ModelKey = key;
var model = this.modelCache.Lookup(key);
if (model != null)
{
//
// Apply model synchronously.
//
this.modelCache.Add(key, model);
this.Model = model;
ApplyModel(true);
}
else
{
if (this.tokenSourceForCurrentTask != null)
{
//
// Another asynchronous load/bind operation is ongoing.
// Cancel that one because we won't need its result.
//
CommonTraceSource.Log.TraceVerbose("Cancelling previous model load task");
this.tokenSourceForCurrentTask.Cancel();
this.tokenSourceForCurrentTask = null;
}
//
// Load model.
//
this.tokenSourceForCurrentTask = new CancellationTokenSource();
try
{
this.Model = await LoadModelAsync(key, this.tokenSourceForCurrentTask.Token)
.ConfigureAwait(true); // Back to original (UI) thread.
if (this.Model != null)
{
this.modelCache.Add(key, this.Model);
}
ApplyModel(false);
}
catch (Exception e) when (e.IsCancellation())
{
CommonTraceSource.Log.TraceVerbose("Model load cancelled");
}
}
}
protected Task InvalidateAsync()
{
if (this.ModelKey == null)
{
return Task.CompletedTask;
}
this.modelCache.Remove(this.ModelKey);
return SwitchToModelAsync(this.ModelKey);
}
protected abstract Task<TModel?> LoadModelAsync(
TModelKey key,
CancellationToken token);
protected abstract void ApplyModel(bool cached);
}
}