Skip to content

Commit

Permalink
Merge branch 'GitHub/shuriken/feature/v1.1' into GitHub/shuriken/master
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-damatov committed Aug 13, 2017
2 parents a634c37 + 63c1acb commit 41d826a
Show file tree
Hide file tree
Showing 87 changed files with 4,783 additions and 1,187 deletions.
45 changes: 32 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Xx]64/
[Xx]86/
[Bb]uild/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
Expand All @@ -42,6 +42,7 @@ dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
Expand Down Expand Up @@ -81,6 +82,7 @@ ipch/
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
Expand Down Expand Up @@ -139,13 +141,16 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml

# TODO: Un-comment the next line if you do not want to checkin
# your web deploy settings because they may include unencrypted
# passwords
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
Expand All @@ -166,9 +171,11 @@ csx/
ecf/
rcf/

# Windows Store app package directory
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
Expand All @@ -178,16 +185,20 @@ BundleArtifacts/

# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

Expand Down Expand Up @@ -231,12 +242,20 @@ FakesAssemblies/
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# LightSwitch generated files
GeneratedArtifacts/
ModelManifest.xml

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
75 changes: 75 additions & 0 deletions Docs/Etw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Logging and Performance Monitoring

The [Shuriken](../README.md) library makes extensive use of the Event Tracing for Windows (ETW) for logging as well as for reporting performance.

## Logging

The following table contains the explosed events:

|Event Name|Event ID|Event Level|Opcode|Payload|Message|
---|---:|---|---|---|---
|`MonitorStart`|1|Informational|Start||Monitor has been started.|
|`MonitorStop`|2|Informational|Stop||Monitor has been stopped.|
|`MonitorSuspend`|3|Informational|Suspend||Monitor has been suspended.|
|`MonitorResume`|4|Informational|Resume||Monitor has been resumed.|
|`StoppingDueToFailedUpdate`|5|Error|Info|`exception`|Stopping the monitoring because of an exception while updating the values: {0}|
|`StoppingDueToFailedChangeNotifications`|6|Error|Info|`exception`|Stopping the monitoring because of an exception while sending the change notifications: {0}|
|`FailedAttachingSystemEvent`|7|Warning|Info|`systemEvent`, `exception`|Failed attaching the system event '{0}': {1}|
|`MissingMonitoringScope`|8|Warning|Info|`eventName`, `scope`|The {0} event handler is assigned, but the {1} is not available.|
|`UnableInitiallyToReadProperty`|9|Warning|Info|`type`, `property`, `exception`|Cannot initially get the value of the '{1}' property of the '{0}' object: {2}|
|`UnableSubsequentlyToReadProperty`|10|Warning|Info|`type`, `property`, `exception`|Cannot get the value of the '{1}' property of the '{0}' object: {2}|
|`UnableInitiallyToInvokeCommandMethod`|11|Warning|Info|`type`, `property`, `method`, `exception`|Cannot initially invoke the '{2}' method of the '{1}' property of the '{0}' object: {3}|
|`UnableSubsequentlyToInvokeCommandMethod`|12|Warning|Info|`type`, `property`, `method`, `exception`|Cannot invoke the '{2}' method of the '{1}' property of the '{0}' object: {3}|
|`UnableToAnalyzeProperty`|13|Warning|Info|`type`, `property`, `exception`|Cannot analyze the value of the '{1}' property of the '{0}' object: {2}|
|`UnableToRaisePropertyChangeNotification`|14|Warning|Info|`type`, `property`, `exception`|Cannot raise the change notification for the '{1}' property of the '{0}' object: {2}|
|`UnableToRaiseCommandPropertyChangeNotification`|15|Warning|Info|`type`, `property`, `exception`|Cannot raise the change notification for the '{1}' command property of the '{0}' object: {2}|
|`CommandFailed`|16|Warning|Info|`exception`|Command execution failed: {0}|

All logging events are traced to the *Operational* channel.

## Measuring Performance

The following table contains the explosed events:

|Event Name|Event ID|Payload|Remarks|
---|---:|---|---
|`PerformanceMonitoredProperties`|19|`count`|Number of monitored properties. Lower value is better. A high value can indicate an unnecessary observation or memory leaks (e.g. due to missing UI virtualization).|
|`PerformanceCycleTime`|20|`elapsedMilliseconds`|Complete cycle time [ms]. Lower value is better. For smooth performance it should not exceed 15ms.|
|`PerformanceLists`|21|`capacityThreadAffine`, `countThreadAffine`, `capacityThreadSafe`, `countThreadSafe`|Total number and number of used slots.|
|`PerformanceListsWithChangedProperties`|22|`capacityThreadAffine`, `countThreadAffine`, `capacityThreadSafe`, `countThreadSafe`|Total number and number of used slots with changed properties.|
|`PerformanceListsWithItemsToBeRemoved`|23|`capacityThreadAffine`, `countThreadAffine`, `capacityThreadSafe`, `countThreadSafe`|Total number and number of used slots with items to be removed.|

All performance-related events are traced to the *Analytic* channel.

## Capturing Events from the Application

Use the static events of the `Shuriken.Diagnostics.EventListener` class to receive notifications when events are traced.

### Using the Output Window in Visual Studio

Capture events from the *Operational* channel and create debug message using the `ToDebugMessage` extension method (also defined in the `Shuriken.Diagnostics.EventListener` class). Consider surrounding the tracing with the `#if DEBUG` directive when the event handler only invokes the `Debug.WriteLine` method:

```csharp
[STAThread]
static void Main()
{
#if DEBUG
Shuriken.Diagnostics.EventListener.OperationalEvent += (_, e) => Debug.WriteLine(e.ToDebugMessage());
#endif

var app = new App();
app.InitializeComponent();

var applicationMonitorScope = new ApplicationMonitorScope(new WpfNotificationContext(app.Dispatcher));
try
{
app.Run();
}
finally
{
applicationMonitorScope.Dispose().GetAwaiter().GetResult();
}
}
```

*Note:* use this approach to be notified when observed properties, commands' `CanExecute` and `Execute` methods throw exceptions.
20 changes: 20 additions & 0 deletions Docs/Guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Guidelines

The [Shuriken](../README.md) library provides the base class `ObservableObject` (which implements the `INotifyPropertyChanged` interface).

In order to provide public observable properties (e.g. for binding) use the following guidelines to define properties:

|Case|Guideline|Behavior when annotated with the `[Observable]` attribute|
---|---|---
|Regular properties|**Always** annotate the property with the `[Observable]` attribute.|The property values are tracked automatically.|
|Immutable properties|**Never** annotate the property with the `[Observable]` attribute. It's not needed to track the property values as they never change.|The property values are tracked, however, as property values never change it just consumes valuable resources.|
|Indexer|**Never** annotate the property with the `[Observable]` attribute. An indexer cannot be tracked automatically. Use the `NotifyIndexerChange` method to send notifications.|The indexer values are *not* tracked.|
|`Command` properties\*|**Consider** annotating the property with the `[Observable]` attribute. Even if the property is immutable (never changes) the `CanExecute` can change. However, if the `CanExecute` never changes the property should not be annotated with the `[Observable]` attribute.|The property values (the `Command` objects) as well as the `CanExecute` are tracked automatically.<br>*Note:* the `CanExecute` always returns `false` while the command is being executed.|
|`AsyncCommand` properties\*|**Always** annotate the property with the `[Observable]` attribute. Even if the property is immutable (never changes) the `CanExecute` can change.|The property values (the `AsyncCommand` objects) as well as the `CanExecute` are tracked automatically.<br>*Note:* the `CanExecute` always returns `false` while the command is being executed.|
|`Command<T>` properties\*|**Consider not** annotating the property with the `[Observable]` attribute. Changes of `CanExecute(T)` cannot be tracked automatically. Use the `NotifyCanExecuteChanged` method to send notifications. Only if the property is not immutable it should be annotated with the `[Observable]` attribute.|The property values (the `Command<T>` objects) are tracked, but the `CanExecute(T)` are not tracked.<br>*Note:* the `CanExecute(T)` always returns `false` while the command is being executed.|
|`AsyncCommand<T>` properties\*|**Always** annotate the property with the `[Observable]` attribute. Even if the property is immutable (never changes) and the `CanExecute(T)` cannot be tracked automatically the method will always return `false` while the command is being executed.|The property values (the `AsyncCommand<T>` objects) are tracked, but the `CanExecute(T)` are not tracked.<br>*Note:* the `CanExecute(T)` always returns `false` while the command is being executed.|
|`ICommand` properties\*|**Consider not** annotating the property with the `[Observable]` attribute. Changes of `CanExecute(object)` cannot be tracked automatically. Only if the property is not immutable it should be annotated with the `[Observable]` attribute.|The property values (the `ICommand` objects) are tracked, but the `CanExecute(object)` are not tracked.|

\* declared property types

*Note:* the `[Observable]` annotation does nothing if the class doesn't derive from the `ObservableObject`
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Shuriken [![NuGet](https://img.shields.io/nuget/v/Shuriken.svg)](https://www.nuget.org/packages/Shuriken) [![ReSharper-Gallery](https://img.shields.io/badge/resharper--gallery-v1.0.0-lightgrey.svg)](https://resharper-plugins.jetbrains.com/packages/Shuriken.Annotations)
# Shuriken [![NuGet](https://img.shields.io/nuget/v/Shuriken.svg)](https://www.nuget.org/packages/Shuriken) [![ReSharper-Gallery](https://img.shields.io/badge/resharper--gallery-v1.1.0-lightgrey.svg)](https://resharper-plugins.jetbrains.com/packages/Shuriken.Annotations)

Fully automated MVVM library without code rewriting. There is no magic behind it: a background thread monitors object properties (explicitly annotated as `[Observable]`), checks their values by comparing with their previous values, and raises change notifications "in the name" of the object.

Expand All @@ -16,13 +16,13 @@ The library compares the current property values with their previous value and c

### Phase 3: Raising change notifications

If the list of changed properties is not empty a single context switch is made to the UI thread to raise all change notifications. The change notifications are always raised in the UI thread even for objects that are known to be thread-safe.
If the list of changed properties is not empty, a single context switch is made to the UI thread to raise all change notifications. The change notifications are always raised in the UI thread even for objects that are known to be thread-safe.

### Registration

The _object type_ registration is triggered automatically when the `PropertyChanged` event is attached for the first time. The object type is scanned for public properties that are annotated with the `[Observable]` attribute. For each discovered property a static method is emitted. The scanning result is cached for that object type.

When the first `PropertyChanged` event is attached the object is treated as _observed_. The library uses weak references to observed objects. When the last `PropertyChanged` event is detached the library stops the object observation.
When the first `PropertyChanged` event is attached, the object is treated as _observed_. The library uses weak references to observed objects. When the last `PropertyChanged` event is detached, the library stops the object observation.

## Examples

Expand Down Expand Up @@ -83,7 +83,7 @@ static void Main()

## Major Benefits
- No need to call `NotifyPropertyChange` in property setters, just annotate the property with `[Observable]` attribute. The library tracks property values, compares them with their previous value and issues property change notification (if needed) in the name of the object.
- No need to to track internal dependencies between observable properties, i.e. "if the property `X` changes the property `Y` must be notified as well". This ensures less error-prone code.
- No need to track internal dependencies between observable properties, i.e. "if the property `X` changes the property `Y` must be notified as well". This ensures less error-prone code.
- No need to notify `Command` properties that the `CanExecute` method can return another value.

## Best Practices
Expand All @@ -95,7 +95,16 @@ static void Main()
- use virtualization where possible
- consider suspending when the app is minimized
- do not annotate indexers with `[Observable]` attribute
- the `[Observable]` annotation does nothing if the class doesn't derive from the `ObservableObject`

See [In-depth look into the guidelines](Docs/Guidelines.md)

## Logging and Performance Monitoring

The Shuriken library makes extensive use of the Event Tracing for Windows (ETW) for logging as well as for reporting performance.

*Note:* when observed properties throw exceptions the monitoring is not interrupted, the exceptions are just logged. The same approach is also applied when command `CanExecute` or `Execute` methods fail. So it is strongly recommended to turn on event capturing and writing to the Output window (at least for debug sessions).

See [In-depth look into logging and performance monitoring](Docs/Etw.md)

## Installation
Use the NuGet package manager to install the package.
Expand Down
11 changes: 7 additions & 4 deletions Sources/Demo.Shuriken.Wpf/Demo.Shuriken.Wpf.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@
<StartupObject>Demo.Shuriken.Wpf.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="JetBrains.Annotations, Version=10.2.1.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>..\packages\JetBrains.Annotations.10.2.1\lib\net\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
<Reference Include="JetBrains.Annotations, Version=11.0.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>..\packages\JetBrains.Annotations.11.0.0\lib\net20\JetBrains.Annotations.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
Expand Down Expand Up @@ -80,7 +79,6 @@
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<None Include="packages.config" />
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
Expand All @@ -89,6 +87,11 @@
<Name>Shuriken.Wpf</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
Loading

0 comments on commit 41d826a

Please sign in to comment.