-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
View locator changes #1311
View locator changes #1311
Conversation
src/ReactiveUI/ViewLocator.cs
Outdated
@@ -93,31 +95,27 @@ public DefaultViewLocator(Func<string, string> viewModelToViewFunc = null) | |||
public IViewFor ResolveView<T>(T viewModel, string contract = null) | |||
where T : class | |||
{ | |||
var view = this.AttemptViewResolutionFor(viewModel.GetType(), contract); | |||
var view = GetCandidateViewModelTypes(viewModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intermediate state is possibly clearer from a review point of view, the big change is adding handling around vm.GetType()
for the case that we fall through to ToggleViewModel type (i.e. respect the runtime type)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it reads better, but I will say that I started with an enumerable in my own implementation, but then removed it to avoid allocs. I haven't double-checked, but I'm pretty sure the previous implementation doesn't alloc (except Type
objects, which I believe will be cached by the runtime) whereas this one will alloc with every view resolution.
Do we care? I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an interesting point, and quite probably right - I'm happy to not use enumerables, and just do the set of null checks if we're concerned? (The last commit is the refactoring, but shouldn't functionally change things)
I can try and do some benchmarks, not sure how much this will tell us though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, benchmarkdotnet could tell us precisely what's going on in terms of memory allocs. Only problem is, we don't really have the infrastructure in place to run it in the solution as it stands. You could easily add a quick check, but it might be throwaway code until we formally decide how to incorporate perf tests into the solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark results
Host Process Environment Information:
BenchmarkDotNet.Core=v0.9.9.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Xeon(R) CPU E3-1231 v3 3.40GHz, ProcessorCount=8
Frequency=3320309 ticks, Resolution=301.1768 ns, Timer=TSC
CLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
GC=Concurrent Workstation
JitModules=clrjit-v4.7.2046.0
Type=ViewResolutionBenchmark Mode=Throughput
Method | Median | StdDev | Scaled | Scaled-SD | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
---------------------- |---------- |---------- |------- |---------- |------- |------ |------ |------------------- |
Default | 5.0061 us | 0.0359 us | 1.00 | 0.00 | 610.00 | - | - | 890.47 |
Pr1311 | 4.9428 us | 0.0277 us | 0.99 | 0.01 | 610.20 | - | - | 888.82 |
EnumerableViewLocator | 5.3197 us | 0.1377 us | 1.06 | 0.03 | 687.45 | - | - | 1,000.36 |
Where Pr1311 is (solid naming, I know) the functional changes, without IEnumerable, and the Enumerable test is with wrapping into an IEnumerable.
public interface IFooViewModel { }
public class FooViewModel : ReactiveObject, IFooViewModel { }
public interface IFooView : IViewFor<IFooViewModel> { }
public class FooView : IFooView
{
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (IFooViewModel)value; }
}
public IFooViewModel ViewModel { get; set; }
}
private readonly ModernDependencyResolver resolver = new ModernDependencyResolver();
private readonly IDisposable resolverDisposable;
private readonly DefaultViewLocator defaultViewLocator = new DefaultViewLocator();
private readonly PR1311ViewLocator pr1311ViewLocator = new PR1311ViewLocator();
private readonly EnumerableViewLocator enumerableViewLocator = new EnumerableViewLocator();
private readonly FooViewModel viewModel = new FooViewModel();
public ViewResolutionBenchmark()
{
resolver.InitializeSplat();
resolver.InitializeReactiveUI();
resolver.Register(() => new FooView(), typeof(IViewFor<FooViewModel>));
resolverDisposable = resolver.WithResolver();
}
[Benchmark(Baseline = true)]
public object Default() => defaultViewLocator.ResolveView(viewModel);
[Benchmark]
public object Pr1311() => pr1311ViewLocator.ResolveView(viewModel);
[Benchmark]
public object EnumerableViewLocator() => enumerableViewLocator.ResolveView(viewModel);
public void Dispose()
{
resolverDisposable.Dispose();
}
We're making more allocations (10%) but I think it'll depend on route through, I can do more benchmarks if wanted, but on the basis of this I'm happy to not do the Enumerable refactoring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this @olevett. Yeah, agreed, we should avoid alloc'ing where possible, especially at such a low level of the framework. Every little bit helps and all. I know we haven't been particularly vigilant about this kind of thing elsewhere, but it might be time to tighten up. This will be easier to manage once we formalise the inclusion of Benchmarkdotnet into the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed without the IEnumerable now
src/ReactiveUI/ViewLocator.cs
Outdated
|
||
this.Log().Warn("Failed to resolve view for view model type '{0}'.", typeof(T).FullName); | ||
return null; | ||
} | ||
|
||
private IEnumerable<Type> GetCandidateViewModelTypes<T>(T viewModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this refactoring makes it a little clearer what we're actually doing, happy to revert it though.
src/ReactiveUI/ViewLocator.cs
Outdated
private IViewFor AttemptViewResolutionFor(Type viewModelType, string contract) | ||
{ | ||
if (viewModelType == null) return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This short circuit handles #1298 we could instead handle the null better out of ToggleViewModelType, but I think it's a reasonable thing to have.
Maybe it would be a good idea to split it into two parts, ViewTypeLocator and ViewLocator. |
@Silv3rcircl3 I quite like that as an idea, might be worth raising as a separate ticket, in the spirit of smaller changes. |
@olevett cool, I'll send a PR once this one was merged |
src/ReactiveUI/ViewLocator.cs
Outdated
@@ -93,31 +95,27 @@ public DefaultViewLocator(Func<string, string> viewModelToViewFunc = null) | |||
public IViewFor ResolveView<T>(T viewModel, string contract = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do the API docs need to be updated inline with the functional changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a quick double check, and have made the only needed change (I think the docs included more functionality than the code did!)
c5ed68d
to
a97270b
Compare
Changes Unknown when pulling a97270b on olevett:view-locator-changes into ** on reactiveui:develop**. |
Changes Unknown when pulling 34505a9 on olevett:view-locator-changes into ** on reactiveui:develop**. |
Thanks @olevett - much appreciated! |
View locator improvements
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Fix
What is the current behavior? (You can also link to an open issue here)
#1299 and #1298 the view locator doesn't correctly resolve a couple of cases, and can't handle oddly named interfaces well.
What is the new behavior (if this is a feature change)?
Handle IRoutableViewModel style VMs, and handle interfaces that don't meet conventions.
Does this PR introduce a breaking change?
It sort of undoes a breaking change introduced in 7.2 with the refactorings around the view locator.
Please check if the PR fulfills these requirements
Other information:
This could conceivably be two separate PRs, but I think it's actually clearer to do it in one