Im currently facing a weird problem with the Microsoft Kinect SDK 2.0 Browser Sample code of Discrete Gesture Basics-WPF
Objective: is to add my own recording of the gesture in filename.gbd into the database and able to extract the gesture inside.
Problem: When i changed the initial name from 'Seated.gbd' to 'RightHandHandsUp.gbd' The error occured below.
However, if i were to rename my 'RightHandHandsUp.gbd' file to 'Seated.gbd' ( the default), it would work. Which is weird, but i suspect there is some binding somewhere in the program but i have already tried searching the entire project for the keyword.
Thanks in advance!!
Error Message:
System.Windows.Markup.XamlParseException: ''The invocation of the constructor on type 'Microsoft.Samples.Kinect.DiscreteGestureBasics.MainWindow' that matches the specified binding constraints threw an exception.' Line number '4' and line position '9'.'
With 2 inner exceptions:
InvalidOperationException: This API has returned an exception from an HRESULT: 0x80004005
And
COMException: Error HRESULT E_FAIL has been returned from a call to a COM component.
so in the GestureDetector.cs file where i changed the name of the file.
//------------------------------------------------------------------------------
// <copyright file="GestureDetector.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.DiscreteGestureBasics
{
using System;
using System.Collections.Generic;
using Microsoft.Kinect;
using Microsoft.Kinect.VisualGestureBuilder;
/// <summary>
/// Gesture Detector class which listens for VisualGestureBuilderFrame events from the service
/// and updates the associated GestureResultView object with the latest results for the 'Seated' gesture
/// </summary>
public class GestureDetector : IDisposable
{
/// <summary> Path to the gesture database that was trained with VGB </summary>
private readonly string gestureDatabase = #"Database\RightHandHandsUp.gbd";
/// <summary> Name of the discrete gesture in the database that we want to track </summary>
private readonly string seatedGestureName = "HandsUp_Right";
/// <summary> Gesture frame source which should be tied to a body tracking ID </summary>
private VisualGestureBuilderFrameSource vgbFrameSource = null;
/// <summary> Gesture frame reader which will handle gesture events coming from the sensor </summary>
private VisualGestureBuilderFrameReader vgbFrameReader = null;
/// <summary>
/// Initializes a new instance of the GestureDetector class along with the gesture frame source and reader
/// </summary>
/// <param name="kinectSensor">Active sensor to initialize the VisualGestureBuilderFrameSource object with</param>
/// <param name="gestureResultView">GestureResultView object to store gesture results of a single body to</param>
public GestureDetector(KinectSensor kinectSensor, GestureResultView gestureResultView)
{
if (kinectSensor == null)
{
throw new ArgumentNullException("kinectSensor");
}
if (gestureResultView == null)
{
throw new ArgumentNullException("gestureResultView");
}
this.GestureResultView = gestureResultView;
// create the vgb source. The associated body tracking ID will be set when a valid body frame arrives from the sensor.
this.vgbFrameSource = new VisualGestureBuilderFrameSource(kinectSensor, 0);
this.vgbFrameSource.TrackingIdLost += this.Source_TrackingIdLost;
// open the reader for the vgb frames
this.vgbFrameReader = this.vgbFrameSource.OpenReader();
if (this.vgbFrameReader != null)
{
this.vgbFrameReader.IsPaused = true;
this.vgbFrameReader.FrameArrived += this.Reader_GestureFrameArrived;
}
// load the 'Seated' gesture from the gesture database
using (VisualGestureBuilderDatabase database = new VisualGestureBuilderDatabase(this.gestureDatabase))
{
// we could load all available gestures in the database with a call to vgbFrameSource.AddGestures(database.AvailableGestures),
// but for this program, we only want to track one discrete gesture from the database, so we'll load it by name
foreach (Gesture gesture in database.AvailableGestures)
{
if (gesture.Name.Equals(this.seatedGestureName))
{
this.vgbFrameSource.AddGesture(gesture);
}
}
}
}
/// <summary> Gets the GestureResultView object which stores the detector results for display in the UI </summary>
public GestureResultView GestureResultView { get; private set; }
/// <summary>
/// Gets or sets the body tracking ID associated with the current detector
/// The tracking ID can change whenever a body comes in/out of scope
/// </summary>
public ulong TrackingId
{
get
{
return this.vgbFrameSource.TrackingId;
}
set
{
if (this.vgbFrameSource.TrackingId != value)
{
this.vgbFrameSource.TrackingId = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether or not the detector is currently paused
/// If the body tracking ID associated with the detector is not valid, then the detector should be paused
/// </summary>
public bool IsPaused
{
get
{
return this.vgbFrameReader.IsPaused;
}
set
{
if (this.vgbFrameReader.IsPaused != value)
{
this.vgbFrameReader.IsPaused = value;
}
}
}
/// <summary>
/// Disposes all unmanaged resources for the class
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the VisualGestureBuilderFrameSource and VisualGestureBuilderFrameReader objects
/// </summary>
/// <param name="disposing">True if Dispose was called directly, false if the GC handles the disposing</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (this.vgbFrameReader != null)
{
this.vgbFrameReader.FrameArrived -= this.Reader_GestureFrameArrived;
this.vgbFrameReader.Dispose();
this.vgbFrameReader = null;
}
if (this.vgbFrameSource != null)
{
this.vgbFrameSource.TrackingIdLost -= this.Source_TrackingIdLost;
this.vgbFrameSource.Dispose();
this.vgbFrameSource = null;
}
}
}
/// <summary>
/// Handles gesture detection results arriving from the sensor for the associated body tracking Id
/// </summary>
/// <param name="sender">object sending the event</param>
/// <param name="e">event arguments</param>
private void Reader_GestureFrameArrived(object sender, VisualGestureBuilderFrameArrivedEventArgs e)
{
VisualGestureBuilderFrameReference frameReference = e.FrameReference;
using (VisualGestureBuilderFrame frame = frameReference.AcquireFrame())
{
if (frame != null)
{
// get the discrete gesture results which arrived with the latest frame
IReadOnlyDictionary<Gesture, DiscreteGestureResult> discreteResults = frame.DiscreteGestureResults;
if (discreteResults != null)
{
// we only have one gesture in this source object, but you can get multiple gestures
foreach (Gesture gesture in this.vgbFrameSource.Gestures)
{
if (gesture.Name.Equals(this.seatedGestureName) && gesture.GestureType == GestureType.Discrete)
{
DiscreteGestureResult result = null;
discreteResults.TryGetValue(gesture, out result);
if (result != null)
{
// update the GestureResultView object with new gesture result values
this.GestureResultView.UpdateGestureResult(true, result.Detected, result.Confidence);
}
}
}
}
}
}
}
/// <summary>
/// Handles the TrackingIdLost event for the VisualGestureBuilderSource object
/// </summary>
/// <param name="sender">object sending the event</param>
/// <param name="e">event arguments</param>
private void Source_TrackingIdLost(object sender, TrackingIdLostEventArgs e)
{
// update the GestureResultView object to show the 'Not Tracked' image in the UI
this.GestureResultView.UpdateGestureResult(false, false, 0.0f);
}
}
}
In my case I didn't know that DiscreteGestureBasics is using AdaBoostTech.dll and RFRProgressTech.dll. I copied directory vgtech from DiscreteGestureBuilder example bin directory to my other project bin directory and everything works fine.
Related
I have a view model and field and I want to send field another view model. I cant use object initialize because the constructor executions first and this field is null. I need this field in the constructor. I tried constructor parameter but that didn't work I get a error;
'ProductDetailViewModel' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TVm' in the generic type or method 'BasePage<TVm>'
P.S: sorry for my English :|
My Base Page:
/// <summary>
/// The base page for all pages to gain base functionality
/// </summary>
public class BasePage : UserControl
{
#region Private Member
/// <summary>
/// The View Model associated with this page
/// </summary>
private object mViewModel;
#endregion
#region Public Properties
/// <summary>
/// The animation the play when the page is first loaded
/// </summary>
public PageAnimation PageLoadAnimation { get; set; } = PageAnimation.SlideAndFadeInFromRight;
/// <summary>
/// The animation the play when the page is unloaded
/// </summary>
public PageAnimation PageUnloadAnimation { get; set; } = PageAnimation.SlideAndFadeOutToLeft;
/// <summary>
/// The time any slide animation takes to complete
/// </summary>
public float SlideSeconds { get; set; } = 0.4f;
/// <summary>
/// A flag to indicate if this page should animate out on load.
/// Useful for when we are moving the page to another frame
/// </summary>
public bool ShouldAnimateOut { get; set; }
/// <summary>
/// The View Model associated with this page
/// </summary>
public object ViewModelObject
{
get => mViewModel;
set
{
// If nothing has changed, return
if (mViewModel == value)
return;
// Update the value
mViewModel = value;
// Fire the view model changed method
OnViewModelChanged();
// Set the data context for this page
DataContext = mViewModel;
}
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage()
{
// Don't bother animating in design time
if (DesignerProperties.GetIsInDesignMode(this))
return;
// If we are animating in, hide to begin with
if (PageLoadAnimation != PageAnimation.None)
Visibility = Visibility.Collapsed;
// Listen out for the page loading
Loaded += BasePage_LoadedAsync;
}
#endregion
#region Animation Load / Unload
/// <summary>
/// Once the page is loaded, perform any required animation
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BasePage_LoadedAsync(object sender, System.Windows.RoutedEventArgs e)
{
// If we are setup to animate out on load
if (ShouldAnimateOut)
// Animate out the page
await AnimateOutAsync();
// Otherwise...
else
// Animate the page in
await AnimateInAsync();
}
/// <summary>
/// Animates the page in
/// </summary>
/// <returns></returns>
public async Task AnimateInAsync()
{
// Make sure we have something to do
if (PageLoadAnimation == PageAnimation.None)
return;
switch (PageLoadAnimation)
{
case PageAnimation.SlideAndFadeInFromRight:
// Start the animation
await this.SlideAndFadeInAsync(AnimationSlideInDirection.Right, false, SlideSeconds,
size: (int)Application.Current.MainWindow.Width);
break;
}
}
/// <summary>
/// Animates the page out
/// </summary>
/// <returns></returns>
public async Task AnimateOutAsync()
{
// Make sure we have something to do
if (PageUnloadAnimation == PageAnimation.None)
return;
switch (PageUnloadAnimation)
{
case PageAnimation.SlideAndFadeOutToLeft:
// Start the animation
await this.SlideAndFadeOutAsync(AnimationSlideInDirection.Left, SlideSeconds);
break;
}
}
#endregion
/// <summary>
/// Fired when the view model changes
/// </summary>
protected virtual void OnViewModelChanged()
{
}
}
/// <summary>
/// A base page with added ViewModel support
/// </summary>
public class BasePage<TVm> : BasePage
where TVm : BaseViewModel, new()
{
#region Public Properties
/// <summary>
/// The view model associated with this page
/// </summary>
public TVm ViewModel
{
get => (TVm)ViewModelObject;
set => ViewModelObject = value;
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage() : base()
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
/// <summary>
/// Constructor with specific view model
/// </summary>
/// <param name="specificViewModel">The specific view model to use, if any</param>
public BasePage(TVm specificViewModel = null) : base()
{
// Set specific view model
if (specificViewModel != null)
ViewModel = specificViewModel;
else
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
{
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
}
}
#endregion
}
My Base View Model:
/// <summary>
/// A base view model that fires Property Changed events as needed
/// </summary>
public class BaseViewModel : INotifyPropertyChanged
{
#region Protected Members
/// <summary>
/// A global lock for property checks so prevent locking on different instances of expressions.
/// Considering how fast this check will always be it isn't an issue to globally lock all callers.
/// </summary>
protected object mPropertyValueCheckLock = new object();
#endregion
/// <summary>
/// The event that is fired when any child property changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
/// <summary>
/// Call this to fire a <see cref="PropertyChanged"/> event
/// </summary>
/// <param name="name"></param>
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#region Command Helpers
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <returns></returns>
protected async Task RunCommandAsync(Expression<Func<bool>> updatingFlag, Func<Task> action)
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <typeparam name="T">The type the action returns</typeparam>
/// <returns></returns>
protected async Task<T> RunCommandAsync<T>(Expression<Func<bool>> updatingFlag, Func<Task<T>> action, T defaultValue = default(T))
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return defaultValue;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
return await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
#endregion
}
View Model Constructor:
View Model
Below is the standard d3drenderer.cs file coding:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Linq;
namespace WPFMediaKit.DirectShow.Controls
{
/// <summary>
/// The D3DRenderer class provides basic functionality needed
/// to render a D3D surface. This class is abstract.
/// </summary>
public abstract class D3DRenderer : FrameworkElement
{
private string workingDirectory = AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// The D3DImage used to render video
/// </summary>
private D3DImage m_d3dImage;
/// <summary>
/// The Image control that has the source
/// to the D3DImage
/// </summary>
private Image m_videoImage;
/// <summary>
/// We keep reference to the D3D surface so
/// we can delay loading it to avoid a black flicker
/// when loading new media
/// </summary>
private IntPtr m_pBackBuffer = IntPtr.Zero;
/// <summary>
/// Flag to tell us if we have a new D3D
/// Surface available
/// </summary>
private bool m_newSurfaceAvailable;
/// <summary>
/// A weak reference of D3DRenderers that have been cloned
/// </summary>
private readonly List<WeakReference> m_clonedD3Drenderers = new List<WeakReference>();
/// <summary>
/// Backing field for the RenderOnCompositionTargetRendering flag.
/// </summary>
private bool m_renderOnCompositionTargetRendering;
/// <summary>
/// Temporary storage for the RenderOnCompositionTargetRendering flag.
/// This is used to remember the value for when the control is loaded and unloaded.
/// </summary>
private bool m_renderOnCompositionTargetRenderingTemp;
#region Stretch
public static readonly DependencyProperty StretchProperty =
DependencyProperty.Register("Stretch", typeof(Stretch), typeof(D3DRenderer),
new FrameworkPropertyMetadata(Stretch.Uniform,
new PropertyChangedCallback(OnStretchChanged)));
/// <summary>
/// Defines what rules are applied to the stretching of the video
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((D3DRenderer)d).OnStretchChanged(e);
}
private void OnStretchChanged(DependencyPropertyChangedEventArgs e)
{
m_videoImage.Stretch = (Stretch) e.NewValue;
}
#endregion
#region IsRenderingEnabled
public static readonly DependencyProperty IsRenderingEnabledProperty =
DependencyProperty.Register("IsRenderingEnabled", typeof(bool), typeof(D3DRenderer),
new FrameworkPropertyMetadata(true));
/// <summary>
/// Enables or disables rendering of the video
/// </summary>
public bool IsRenderingEnabled
{
get { return (bool)GetValue(IsRenderingEnabledProperty); }
set { SetValue(IsRenderingEnabledProperty, value); }
}
#endregion
#region NaturalVideoHeight
private static readonly DependencyPropertyKey NaturalVideoHeightPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoHeight", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoHeightProperty
= NaturalVideoHeightPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel height of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoHeight
{
get { return (int)GetValue(NaturalVideoHeightProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoHeight DP
/// </summary>
protected void SetNaturalVideoHeight(int value)
{
SetValue(NaturalVideoHeightPropertyKey, value);
}
#endregion
#region NaturalVideoWidth
private static readonly DependencyPropertyKey NaturalVideoWidthPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoWidth", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoWidthProperty
= NaturalVideoWidthPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel width of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoWidth
{
get { return (int)GetValue(NaturalVideoWidthProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoWidth DP
/// </summary>
protected void SetNaturalVideoWidth(int value)
{
SetValue(NaturalVideoWidthPropertyKey, value);
}
#endregion
#region HasVideo
private static readonly DependencyPropertyKey HasVideoPropertyKey
= DependencyProperty.RegisterReadOnly("HasVideo", typeof(bool), typeof(MediaElementBase),
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty HasVideoProperty
= HasVideoPropertyKey.DependencyProperty;
/// <summary>
/// Is true if the media contains renderable video
/// </summary>
public bool HasVideo
{
get { return (bool)GetValue(HasVideoProperty); }
}
/// <summary>
/// Internal method for setting the read-only HasVideo DP
/// </summary>
protected void SetHasVideo(bool value)
{
SetValue(HasVideoPropertyKey, value);
}
#endregion
protected D3DRenderer()
{
InitializeD3DVideo();
/* Hook into the framework events */
Loaded += D3DRenderer_Loaded;
Unloaded += D3DRenderer_Unloaded;
}
/// <summary>
/// Handler for when the D3DRenderer is unloaded
/// </summary>
private void D3DRenderer_Unloaded(object sender, RoutedEventArgs e)
{
/* Remember what the property value was */
m_renderOnCompositionTargetRenderingTemp = RenderOnCompositionTargetRendering;
/* Make sure to unhook the static event hook because we are unloading */
RenderOnCompositionTargetRendering = false;
}
/// <summary>
/// Handler for when the D3DRenderer is loaded
/// </summary>
private void D3DRenderer_Loaded(object sender, RoutedEventArgs e)
{
/* Restore the property's value */
RenderOnCompositionTargetRendering = m_renderOnCompositionTargetRenderingTemp;
}
/// <summary>
/// Initializes the D3DRenderer control
/// </summary>
protected virtual void InitializeD3DVideo()
{
if (m_videoImage != null)
return;
/* Create our Image and it's D3DImage source */
m_videoImage = new Image();
m_d3dImage = new D3DImage();
/* We hook into this event to handle when a D3D device is lost */
D3DImage.IsFrontBufferAvailableChanged += D3DImage_IsFrontBufferAvailableChanged;
/* Set our default stretch value of our video */
m_videoImage.Stretch = (Stretch)StretchProperty.DefaultMetadata.DefaultValue;
var activeProfiles = XElement.Load(workingDirectory + "ProfilesDisplay.xml");
var a = activeProfiles.Element("_nABC").Value;
/* Our source of the video image is the D3DImage */
m_videoImage.Source = D3DImage;
/* Register the Image as a visual child */
AddVisualChild(m_videoImage);
}
/// <summary>
/// This should only fire when a D3D device is lost
/// </summary>
private void D3DImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!D3DImage.IsFrontBufferAvailable)
return;
/* Flag that we have a new surface, even
* though we really don't */
m_newSurfaceAvailable = true;
/* Force feed the D3DImage the Surface pointer */
SetBackBufferInternal(m_pBackBuffer);
}
protected override Size MeasureOverride(Size availableSize)
{
m_videoImage.Measure(availableSize);
return m_videoImage.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
m_videoImage.Arrange(new Rect(finalSize));
return finalSize;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index > 0)
throw new IndexOutOfRangeException();
return m_videoImage;
}
protected D3DImage D3DImage
{
get
{
return m_d3dImage;
}
}
protected Image VideoImage
{
get
{
return m_videoImage;
}
}
/// <summary>
/// Renders the video with WPF's rendering using the CompositionTarget.Rendering event
/// </summary>
protected bool RenderOnCompositionTargetRendering
{
get
{
return m_renderOnCompositionTargetRendering;
}
set
{
/* If it is being set to true and it was previously false
* then hook into the event */
if (value && !m_renderOnCompositionTargetRendering)
CompositionTarget.Rendering += CompositionTarget_Rendering;
else if(!value)
CompositionTarget.Rendering -= CompositionTarget_Rendering;
m_renderOnCompositionTargetRendering = value;
m_renderOnCompositionTargetRenderingTemp = value;
}
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
InternalInvalidateVideoImage();
}
/// <summary>
/// Used as a clone for a D3DRenderer
/// </summary>
private class ClonedD3DRenderer : D3DRenderer
{}
/// <summary>
/// Creates a clone of the D3DRenderer. This is a work for the visual
/// brush not working cross-threaded
/// </summary>
/// <returns></returns>
public D3DRenderer CloneD3DRenderer()
{
var renderer = new ClonedD3DRenderer();
lock(m_clonedD3Drenderers)
{
m_clonedD3Drenderers.Add(new WeakReference(renderer));
}
renderer.SetBackBuffer(m_pBackBuffer);
return renderer;
}
/// <summary>
/// Cleans up any dead references we may have to any cloned renderers
/// </summary>
private void CleanZombieRenderers()
{
lock(m_clonedD3Drenderers)
{
var deadObjects = new List<WeakReference>();
for (int i = 0; i < m_clonedD3Drenderers.Count; i++)
{
if (!m_clonedD3Drenderers[i].IsAlive)
deadObjects.Add(m_clonedD3Drenderers[i]);
}
foreach(var deadGuy in deadObjects)
{
m_clonedD3Drenderers.Remove(deadGuy);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. The back buffer is
/// not set until we actually receive a frame, this way we
/// can avoid a black flicker between media changes
/// </summary>
/// <param name="backBuffer">The unmanaged pointer to the Direct3D Surface</param>
protected void SetBackBuffer(IntPtr backBuffer)
{
/* We only do this if target rendering is enabled because we must use an Invoke
* instead of a BeginInvoke to keep the Surfaces in sync and Invoke could be dangerous
* in other situations */
if(RenderOnCompositionTargetRendering)
{
if (!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.Invoke((Action)(() => SetBackBuffer(backBuffer)), DispatcherPriority.Render);
return;
}
}
/* Flag a new surface */
m_newSurfaceAvailable = true;
m_pBackBuffer = backBuffer;
/* Make a special case for target rendering */
if (RenderOnCompositionTargetRendering)
{
SetBackBufferInternal(m_pBackBuffer);
}
SetBackBufferForClones();
}
/// <summary>
/// Sets the backbuffer for any cloned D3DRenderers
/// </summary>
private void SetBackBufferForClones()
{
lock (m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach (var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if (renderer != null)
renderer.SetBackBuffer(m_pBackBuffer);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. This happens immediately
/// </summary>
private void SetBackBufferInternal(IntPtr backBuffer)
{
/* Do nothing if we don't have a new surface available */
if (!m_newSurfaceAvailable)
return;
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => SetBackBufferInternal(backBuffer)));
return;
}
/* We have this around a try/catch just in case we
* lose the device and our Surface is invalid. The
* try/catch may not be needed, but testing needs
* to take place before it's removed */
try
{
D3DImage.Lock();
D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer);
D3DImage.Unlock();
SetNaturalWidthHeight();
}
catch (Exception)
{ }
/* Clear our flag, so this won't be ran again
* until a new surface is sent */
m_newSurfaceAvailable = false;
}
private void SetNaturalWidthHeight()
{
SetNaturalVideoHeight(m_d3dImage.PixelHeight);
SetNaturalVideoWidth(m_d3dImage.PixelWidth);
}
protected void InvalidateVideoImage()
{
if (!m_renderOnCompositionTargetRendering)
InternalInvalidateVideoImage();
}
/// <summary>
/// Invalidates the entire Direct3D image, notifying WPF to redraw
/// </summary>
protected void InternalInvalidateVideoImage()
{
/* Ensure we run on the correct Dispatcher */
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => InvalidateVideoImage()));
return;
}
/* If there is a new Surface to set,
* this method will do the trick */
SetBackBufferInternal(m_pBackBuffer);
/* Only render the video image if possible, or if IsRenderingEnabled is true */
if (D3DImage.IsFrontBufferAvailable && IsRenderingEnabled && m_pBackBuffer != IntPtr.Zero)
{
try
{
/* Invalidate the entire image */
D3DImage.Lock();
D3DImage.AddDirtyRect(new Int32Rect(0, /* Left */
0, /* Top */
D3DImage.PixelWidth, /* Width */
D3DImage.PixelHeight /* Height */));
D3DImage.Unlock();
}
catch
{ }
}
/* Invalidate all of our cloned D3DRenderers */
InvalidateClonedVideoImages();
}
/// <summary>
/// Invalidates any possible cloned renderer we may have
/// </summary>
private void InvalidateClonedVideoImages()
{
lock(m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach(var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if(renderer != null)
renderer.InvalidateVideoImage();
}
}
}
}
}
In the InitializeD3DVideo() function, I try to read a xml file from a location from the harddisk. But it can't be done.
Is it that the d3drenderer.cs cannot read from a xml file? I just want to read a value from _nABC of the xml file.
This has nothing to do with the D3DRenderer.
The path to the XML file is wrong. There is a '/' (or '\') missing between the workingDirectory and the + "ProfilesDisplay.xml".
If you don't know your path strings, the best method to combine these is System.IO.Path.Combine.
I am using XMGUnityLib Plugin. The program runs fine in the editor without any error but when I try to build it (Android) then while at pushing apk step it gives this error:
InvalidOperationException: The following game object is invoking the DontDestroyOnLoad method: AnalyticsManager. Notice that DontDestroyOnLoad can only be used in play mode and, as such, cannot be part of an editor script.
XMGSingleton`1[XMGAnalyticsManager].get_Instance () (at Assets/XMG/UnityLib/Util/XMGSingleton.cs:28)
AndroidStorePostProcess.OnPostprocessScene () (at Assets/Editor/AndroidStorePostProcess.cs:32)
UnityEditor.HostView:OnGUI()
XMGSingleton.cs:
using UnityEngine;
using System.Collections;
using System;
/// <summary>
/// XMG singleton base Class.
/// Inherit your Manager Monobehavior classes that you want to be Singletons
/// </summary>
public abstract class XMGSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
/// <summary>
/// Returns the instance of this singleton
/// Make sure we don't get destroyed on Scene Load
/// </summary>
public static T Instance {
get {
if(instance == null) {
instance = (T) FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
return null;
}
DontDestroyOnLoad(instance.gameObject);
}
return instance;
}
}
#region Public Methods
public static bool HasInstance {
get {
if( (T) FindObjectOfType(typeof(T)) != null ) {
return true;
} else {
return false;
}
}
}
/// <summary>
/// Helper to cast the Instance to the Type provided
/// </summary>
public static Type MyInstance<Type> () {
return (Type)(object)Instance;
}
#endregion
}
XMGAnalyticsManager.cs:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Com.XMG.UnityLib.Community.OnlineGame;
using Com.XMG.UnityLib.Tracking;
#if UNITY_IPHONE
using FlurryAccess = FlurryBinding;
#elif UNITY_ANDROID
using FlurryAccess = FlurryAndroid;
#endif
public abstract partial class XMGAnalyticsManager : XMGSingleton<XMGAnalyticsManager> {
#region Private Fields
/// <summary>
/// The type of the current build.
/// For Analytics QA testing point to Analytics
/// For QA Testing point to SandBox
/// For Production/Release build point to Production
/// </summary>
[SerializeField]
private BuildTypeEnum buildType = BuildTypeEnum.SandBox;
/// <summary>
/// The type of the android store type
/// </summary>
[SerializeField]
private AndroidStoreTypeEnum androidStoreType = AndroidStoreTypeEnum.Google;
/// <summary>
/// The swrve authentication Info
/// </summary>
[SerializeField]
private SwrveAuthentication swrveAuth;
/// <summary>
/// The flurry auth.
/// </summary>
[SerializeField]
private FlurryAuthentication flurryAuth;
/// <summary>
/// Sends events every X seconds.
/// </summary>
[SerializeField]
private float sendEventsEverySeconds = 30;
/// <summary>
/// The parent screen.
/// </summary>
[SerializeField]
private string parentScreen = "SplashPage";
/// <summary>
/// The grand parent screen.
/// </summary>
string grandParentScreen = "null";
/// <summary>
/// My external IP.
/// </summary>
string myExtIP = "";
/// <summary>
/// The swrve component.
/// </summary>
private SwrveComponent swrveComponent;
private Dictionary<string, JSONObject> swrveResources = new Dictionary<string, JSONObject>();
/// <summary>
/// Resource hashmap ----> (key: resource name, value: json object)
/// </summary>
public Dictionary<string, JSONObject> Resources {
get {
return this.swrveResources;
}
}
/// <summary>
/// This event is triggered when swrve data is updated.
/// </summary>
public static event Action ItemsConfigurationUpdatedEvent = delegate {};
/// <summary>
/// True if we are in the fetching of AB Test Data State
/// </summary>
private bool isFetchingABParams = false;
/// <summary>
/// The current swrve identifier.
/// </summary>
private string currentSwrveId = null;
/// <summary>
/// The android bundle version.
/// </summary>
[SerializeField]
protected string androidBundleVersion = "1.0";
#endregion
#region Public Properties
/// <summary>
/// True if it's a production build
/// </summary>
public bool IsProductionBuild {
get { return buildType == BuildTypeEnum.Production; }
}
/// <summary>
/// Gets the type of the android store.
/// </summary>
public AndroidStoreTypeEnum AndroidStoreType {
get { return androidStoreType; }
set { androidStoreType = value; }
}
/// <summary>
/// True if its a google play store build
/// </summary>
public bool IsGoogleStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Google; }
}
/// <summary>
/// True if its an Amazon store build
/// </summary>
public bool IsAmazonStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Amazon; }
}
#endregion
#region Monobehaviors & Init
/// <summary>
/// Add event Listeners
/// </summary>
void Awake() {
XMGOnlineProfileManager.OnProfileLoadedEvent += ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent += ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent += NewFriendsAddedCallback;
RegisterGameListeners();
// TODO: Listen for Profile Change events
swrveAuth.Init();
flurryAuth.Init();
}
/// <summary>
/// Starts the SWRVE Event timer
/// </summary>
void Start() {
StartSwrveTimer();
StartCoroutine( CheckIP() );
}
/// <summary>
/// Checks for the IP
/// </summary>
/// <returns>
/// The IP
/// </returns>
IEnumerator CheckIP(){
WWW myExtIPWWW = new WWW("http://checkip.dyndns.org");
if( myExtIPWWW != null ) {
yield return myExtIPWWW;
if( myExtIPWWW.error == null ) {
myExtIP=myExtIPWWW.text;
myExtIP=myExtIP.Substring(myExtIP.IndexOf(":")+1);
myExtIP=myExtIP.Substring(0,myExtIP.IndexOf("<"));
}
}
}
/// <summary>
/// Detach event Listeners
/// </summary>
void OnDestroy() {
XMGOnlineProfileManager.OnProfileLoadedEvent -= ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent -= ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent -= NewFriendsAddedCallback;
UnRegisterGameListeners();
}
/// <summary>
/// If Pause end session
/// If Resume Start session and Re-Login.
/// At any case post events
/// </summary>
/// <param name='pause'>
/// Pause.
/// </param>
void OnApplicationPause(bool pause) {
if (currentSwrveId != null) {
if(pause) {
swrveComponent.Swrve_AddSessionEnd();
} else {
swrveComponent.Swrve_AddSessionStart();
SessionStart();
AnalyticsLogout();
AnalyticsLogin();
}
LogUserProperties();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
/// <summary>
/// Init plugins.The Profile Has loaded
/// </summary>
void ProfileHasLoadedCallback() {
// Get Swrve component
swrveComponent = gameObject.GetComponentInChildren<SwrveComponent>();
AnalyticsLogin();
// Check if we've set the random user property, if not then set it
if (!PlayerPrefs.HasKey(RANDOM_TEST_PROPERTY_KEY)) {
PlayerPrefs.SetString(RANDOM_TEST_PROPERTY_KEY, "true");
JSONObject userProperty = new JSONObject(JSONObject.Type.OBJECT);
int random = NGUITools.RandomRange(1, 100);
userProperty.AddField("RandNum", random.ToString());
swrveComponent.Swrve_AddUserUpdateEvent(userProperty.ToString());
}
}
/// <summary>
/// A new Online Profile created callback.
/// </summary>
void ProfileCreatedOnServerCallback() {
AddNamedEvent( "Community.FacebookUserCreated" );
}
/// <summary>
/// New friends were added callback.
/// </summary>
/// <param name='newFriendsAdded'>
/// The New friends added
/// </param>
void NewFriendsAddedCallback( int newFriendsAdded ) {
JSONObject jTournamentFriends = new JSONObject(JSONObject.Type.OBJECT);
jTournamentFriends.AddField( "NewFriendsAdded", BucketGeneral( newFriendsAdded ) );
AddNamedEvent ( "Community.TournamentAddedFriend", jTournamentFriends );
}
#endregion
#region Abstract & Virtual Methods
/// <summary>
/// Gets the tracking Object
/// </summary>
protected abstract XMGTracking GetTracking();
/// <summary>
/// Registers the game listeners.
/// </summary>
protected abstract void RegisterGameListeners();
/// <summary>
/// Unregister the game listeners.
/// </summary>
protected abstract void UnRegisterGameListeners();
/// <summary>
/// Logs the game user properties.
/// </summary>
/// <param name='jUserProperties'>
/// The JSON Object with the user properties.
/// </param>
protected abstract void LogGameUserProperties(JSONObject jUserProperties);
/// <summary>
/// A session Is Started
/// </summary>
protected abstract void SessionStart();
/// <summary>
/// The session has ended
/// </summary>
protected abstract void SessionEnd();
/// <summary>
/// Gets the user start time.
/// Default to Now
/// Override to provide the user's start time
/// </summary>
/// <returns>
/// The user start time.
/// </returns>
protected virtual long GetUserStartTime() {
return (long)XMGUnityUtil.ToUnixTimeFromUniversal( DateTime.UtcNow );
}
#endregion
#region Private Methods
/// <summary>
/// Starts the swrve timer.
/// </summary>
private void StartSwrveTimer() {
StartCoroutine( StartEventsTimer());
}
/// <summary>
/// Logs the user properties.
/// And Calls abstract LogGameUserProperties
/// </summary>
private void LogUserProperties() {
JSONObject jUserProperties = new JSONObject(JSONObject.Type.OBJECT);
// Add here Common User Properties
XMGTracking tracking = GetTracking();
jUserProperties.AddField( UP_FIRST_LAUNCH_DAY, tracking.FirstLaunch.Day );
jUserProperties.AddField( UP_FIRST_LAUNCH_MONTH, tracking.FirstLaunch.Month );
jUserProperties.AddField( UP_FIRST_LAUNCH_YEAR, tracking.FirstLaunch.Year );
jUserProperties.AddField( UP_FIRST_LAUNCH_EPOCH, XMGUnityUtil.ToUnixTimeFromUniversal( tracking.FirstLaunch ).ToString() );
jUserProperties.AddField( UP_TOTAL_TAPJOY_REWARDS, tracking.TotalTapJoyPointsEarned );
jUserProperties.AddField( UP_TOTAL_SPONSORPAY_REWARDS, tracking.TotalSponsorPayPointsEarned );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLICKED, tracking.TotalChartBoostAdsClicked );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLOSED, tracking.TotalChartBoostAdsClosed );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_EARNED, tracking.SoftCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_EARNED, tracking.HardCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_SPENT, tracking.SoftCurrencySpent );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_SPENT, tracking.HardCurrencySpent );
#if UNITY_IPHONE
jUserProperties.AddField( UP_VERION, EtceteraTwoBinding.getInfoPlistValue("CFBundleVersion") );
#elif UNITY_ANDROID
jUserProperties.AddField( UP_VERION, androidBundleVersion );
#endif
jUserProperties.AddField( UP_TOTAL_FRIENDS, tracking.TotalFriends );
jUserProperties.AddField( UP_TOTAL_UNIQUE_DAYS_PLAYED, tracking.TotalUniqueDaysPlayed );
if( string.IsNullOrEmpty( myExtIP ) ) {
jUserProperties.AddField( UP_IP, myExtIP );
}
LogGameUserProperties(jUserProperties);
swrveComponent.Swrve_AddUserUpdateEvent(jUserProperties.ToString());
}
#endregion
#region Authentication
/// <summary>
/// Logs in to Flurry and SWRVE
/// </summary>
void AnalyticsLogin() {
FlurryLogin();
if( currentSwrveId != null ) {
if( !currentSwrveId.Equals( XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId ) ) {
Debug.Log("There is already a swrve account signed in!!! You must close the session first!");
AnalyticsLogout();
} else {
// This swrve user is already logged in, bail out of this function
Debug.Log("Attempt to log into swrve twice with the same id... Ignoring.");
return;
}
}
SwrveLogin(XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId);
}
/// <summary>
/// Logout from Flurry and SWRVE
/// </summary>
private void AnalyticsLogout() {
FlurryLogout();
if(currentSwrveId == null) {
Debug.LogError("There is no active swrve user!!! Cannot close session.");
return;
}
SwrveLogout();
}
/// <summary>
/// Starts Flurry Session
/// </summary>
private void FlurryLogin() {
#if UNITY_IPHONE
FlurryAccess.startSession(flurryAuth.FlurryKey);
#elif UNITY_ANDROID
FlurryAccess.onStartSession(flurryAuth.FlurryKey, true, true );
#endif
}
/// <summary>
/// Logouts from Flurry
/// </summary>
private void FlurryLogout() {
#if UNITY_ANDROID
FlurryAccess.onEndSession();
#endif
}
/// <summary>
/// Login to Swrve
/// </summary>
/// <param name='swrveId'>
/// The Swrve identifier.
/// </param>
private void SwrveLogin(string swrveId) {
currentSwrveId = swrveId;
int gameId = 0;
string apiKey = "";
if (buildType == BuildTypeEnum.Analytics) {
gameId = swrveAuth.AnalyticsGameID;
apiKey = swrveAuth.AnalyticsAPIKey;
} else if (buildType == BuildTypeEnum.SandBox) {
gameId = swrveAuth.SandboxGameID;
apiKey = swrveAuth.SandboxAPIKey;
} else if( buildType == BuildTypeEnum.Production ) {
gameId = swrveAuth.ProductionGameID;
apiKey = swrveAuth.ProductionAPIKey;
}
InitializeSWRVEComponent(gameId,
apiKey,
swrveAuth.PersonalKey,
swrveAuth.URLABTest
);
}
/// <summary>
/// Logout from Swrve
/// </summary>
private void SwrveLogout() {
// Make sure to close the Swrve session
SessionEnd();
swrveComponent.Swrve_AddSessionEnd();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
currentSwrveId = null;
}
/// <summary>
/// Initializes the SWRVE component.
/// </summary>
/// <param name='gameId'>
/// The Game identifier.
/// </param>
/// <param name='apiKey'>
/// The API key.
/// </param>
/// <param name='personalKey'>
/// Personal key.
/// </param>
/// <param name='abURL'>
/// The Ab Test server URL.
/// </param>
private void InitializeSWRVEComponent(int gameId, string apiKey, string personalKey, string abURL) {
swrveComponent.gameId = gameId;
swrveComponent.apiKey = apiKey;
swrveComponent.abTestServer = abURL;
// Setup swrve for new user
swrveComponent.Swrve_AddSessionStart();
SessionStart();
if(isFetchingABParams == false) {
isFetchingABParams = true;
StartCoroutine(GetAllResourcesWithABTests());
}
}
#endregion
#region A/B Helpers
/// <summary>
/// Returns the most recent JSON configuration for a resource from swrve or NULL if there is none.
/// This means it could return cached data that is old. If you want to retrieve the latest from swrve.
/// </summary>
/// <param name="itemId">
/// A <see cref="System.String"/>
/// </param>
/// <returns>
/// A <see cref="JSONObject"/>
/// </returns>
public JSONObject GetItemParams(string itemId) {
JSONObject item = null;
swrveResources.TryGetValue(itemId, out item);
return item;
}
/// <summary>
/// Gets all resources with AB tests.
/// </summary>
/// <returns>
/// The all resources with AB tests.
/// </returns>
private IEnumerator GetAllResourcesWithABTests() {
string swrveURLRequest = string.Format(swrveAuth.URLABTestResources + "?api_key={0}&user={1}&joined={2}", swrveComponent.apiKey, currentSwrveId, GetUserStartTime() );
WWW itemRequest = new WWW(swrveURLRequest);
yield return itemRequest; // Yield until a result is returned.
if(itemRequest.error != null) {
Debug.LogWarning("Error attempting to fetch Swrve A/B Resource data: " + itemRequest.error);
LoadCachedConfig();
// Bail!
yield break;
}
// Process all the resources into a hashmap ( key: resource name, value: json object )
JSONObject jResources = new JSONObject(itemRequest.text);
if(jResources == null || jResources.type == JSONObject.Type.NULL) {
// Bad data from swrve, abort!
Debug.LogError("Bad A/B resource data from swrve!");
LoadCachedConfig();
yield break;
} else {
//Debug.LogWarning( "Data from SWRVE: " + jResources );
}
XMGSaveLoadUtils.Instance.SaveEncryptedField(CACHED_CONFIG_KEY, itemRequest.text); // Replace the old cached config.
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
isFetchingABParams = false;
}
/// <summary>
/// Loads the cached swrve config file
/// </summary>
private void LoadCachedConfig() {
string cached = XMGSaveLoadUtils.Instance.LoadEncryptedField(CACHED_CONFIG_KEY);
if(string.IsNullOrEmpty(cached)) {
// There was no cached data
return;
}
// Apply the cached resources so it doesn't block updates.
JSONObject jResources = new JSONObject(cached);
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
}
/// <summary>
/// Expects a JSON array of resources.
/// </summary>
/// <param name="resources">
/// A <see cref="JSONObject"/>
/// </param>
private void ParseResources(JSONObject jResources) {
swrveResources.Clear();
//Debug.Log( "Parsing SWRVE Resources: " + jResources );
if(jResources == null || jResources.type != JSONObject.Type.ARRAY) {
// Bad data from swrve, abort!
Debug.LogError("Could not parse resource data, unexpected format!");
return;
}
foreach(JSONObject data in jResources.list) {
JSONObject key = data.GetField("uid");
if(key == null || key.type != JSONObject.Type.STRING || string.IsNullOrEmpty( JSONObject.GetString(key) ) ) {
// Bad item, on to the next
Debug.LogWarning("Bad item, no property 'uid' in " + data.print());
continue;
}
// Add the resource over top of any precached configuration
swrveResources[JSONObject.GetString(key)] = data;
}
}
#endregion
#region Event Handling
/// <summary>
/// Adds the named event.
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
public void AddNamedEvent(string name) {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
#if UNITY_IPHONE
FlurryAccess.logEvent(name, false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name);
#endif
}
/// <summary>
/// Adds a named event with Payload
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
/// <param name='payload'>
/// Payload.
/// </param>
public void AddNamedEvent(string name, JSONObject payload) {
string data = payload.ToString();
if(!string.IsNullOrEmpty(data)) {
swrveComponent.Swrve_AddNamedEvent(name, data);
} else {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
}
#if UNITY_IPHONE
FlurryAccess.logEventWithParameters(name, payload.ToDictionary(), false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name, payload.ToDictionary());
#endif
}
/// <summary>
/// Infinite Loop to send events in the queue
/// </summary>
private IEnumerator StartEventsTimer() {
float timeInterval = Time.realtimeSinceStartup;
while (true) {
if(Time.realtimeSinceStartup - timeInterval >= sendEventsEverySeconds) {
SendEvents();
timeInterval = Time.realtimeSinceStartup;
}
yield return new WaitForSeconds( 1 );
}
}
/// <summary>
/// Sends the events to SWRVE
/// </summary>
private void SendEvents() {
if(currentSwrveId != null) {
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
#endregion
#region UI Events
/// <summary>
/// Logs a UI screen visit event.
/// Records the current screen, the parent screen, and the grandparent screen.
/// </summary>
/// <param name='screenName'>
/// Screen name.
/// </param>
public void ScreenVisitEvent(string screenName) {
JSONObject eventParams = new JSONObject(JSONObject.Type.OBJECT);
if(parentScreen != null) {
eventParams.AddField("parent", parentScreen);
}
if(grandParentScreen != null) {
eventParams.AddField("grandParent", grandParentScreen);
}
AddNamedEvent("UI.Flow." + screenName, eventParams);
eventParams.AddField("screen", screenName);
AddNamedEvent("UI.Flow.Screens", eventParams);
grandParentScreen = parentScreen;
parentScreen = screenName;
}
/// <summary>
/// Logs a UI button pressed event
/// </summary>
/// <param name='buttonName'>
/// The Button name.
/// </param>
public void ButtonPressedEvent( string buttonName ) {
AddNamedEvent("UI.ButtonPressed." + buttonName );
}
#endregion
#region Purchase Events
/// <summary>
/// Sends an analytics buy in event for purchasing inapps.
/// </summary>
/// <param name='rewardCurrency'>
/// Reward currency
/// </param>
/// <param name='rewardAmount'>
/// Amount of rewardCurrency
/// </param>
/// <param name='localCost'>
/// The real money price in local currency ( e.g 0.99 )
/// </param>
/// <param name='localCurrency'>
/// CAD, USD, etc...
/// </param>
public void AddBuyInEvent(string rewardCurrency, int rewardAmount, float localCost, string localCurrency, string itemID ) {
swrveComponent.Swrve_AddBuyInEvent( "", rewardCurrency, rewardAmount, Mathf.Round(localCost* 100)/100, localCurrency );
AddNamedEvent( "Purhases.IAP" );
AddNamedEvent( "Purhases.IAP.HardCurrency" + itemID );
}
/// <summary>
/// Analytics event when a user purchases an item in-game.
/// </summary>
/// <param name='itemID'>
/// The UID for the Item
/// </param>
/// <param name='currency'>
/// The currency
/// </param>
/// <param name='itemCost'>
/// Item cost.
/// </param>
/// <param name='itemQuantity'>
/// Item quantity.
/// </param>
public void AddPurchaseEvent( string itemID, string currency, int itemCost, int itemQuantity ) {
swrveComponent.Swrve_AddPurchaseItemEvent( itemID, currency, itemCost, itemQuantity );
}
/// <summary>
/// Adds a purchase conversion event.
/// </summary>
/// <param name='itemID'>
///The unique Item ID
/// </param>
public void AddPurchaseConversionPackEvent( string itemID ) {
AddNamedEvent( "Purchases.HardCurrency.SoftCurrency" );
AddNamedEvent( "Purhases.HardCurrency.SoftCurrency." + itemID );
}
#endregion
}
Is this singleton class correctly coded? If yes, what could be the problem?
Any help will be appreciated.
Thanks :)
Do you have an "Editor Script" that inherits from this class? If so then most probably this is what's causing the error. If you're not sure what this means, try looking for a folder in your project named Editor and looking in its scripts. Or try searching for any scripts that are using UnityEditor; and that inherit from Editor like this: public class Name : Editor and make them not inherit from this class.
Another solution is try commenting the DontDestroyOnLoad line (line 26 in XMGSingleton.cs) and put it yourself where you need it.
Go to the objects that you're using this script with and do something like this:
void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
}
else
if (instance != this)
Destroy(gameObject);
}
Though the post is a bit old, I recently bumped into this issue. It was a bit confusing because I had not added any new editor script and other editor scripts were not being called from the Singleton GameObject that the error was pointing to.
When I tried restarting Unity and then again created the build, it started giving some other exception.
However, I finally found the issue was being caused by a method from another object (created by the Singleton's OnAwake method) trying to access an uninitialized property of the Singleton (because the OnAwake method execution was not completed).
I fixed it by delaying the other object's method callback based on a different event.
I would like to ask the user to pin to start the default live tile when he runs the app for the first time in Windows 10 (Universal Windows Platform).
I know that for a secondaryTile you can use the following code:
var result = await secondaryTile.RequestCreateAsync();
What is the equivalent for a default live tile?
Since this question was asked, there has been a new addition to the UWP API: StartScreenManager in V4 which lets you pin the app's default tile to the start screen. Here is a Command that lets you do it - and is disabled if the tile is already there. It handles the Window Activated event because the user could remove the pinned tile manually:
using System;
using System.Linq;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.Foundation.Metadata;
using Windows.UI.Core;
using Windows.UI.StartScreen;
using Windows.UI.Xaml;
namespace Synergist
{
/// <summary>
/// Pin the first entry in the package's app list to the start screen
/// </summary>
public class PinToStartCommand : ICommand
{
private bool _canExecute;
/// <summary>
/// Initializes a new instance of the PinToStartCommand class.
/// </summary>
public PinToStartCommand()
{
Window.Current.Activated += Current_Activated;
}
/// <summary>
/// Can execute changed event handler
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// returns true if the StartScreenManager exists
/// </summary>
/// <param name="parameter">the parameter is not used</param>
/// <returns>true if the app is not pinned to the start screen and the API is available</returns>
public bool CanExecute(object parameter)
{
return _canExecute;
}
/// <summary>
/// Pin the app to the start screen
/// </summary>
/// <param name="parameter">the parameter is not used.</param>
public async void Execute(object parameter)
{
if (ApiInformation.IsTypePresent("Windows.UI.StartScreen.StartScreenManager"))
{
var entries = await Package.Current.GetAppListEntriesAsync();
var firstEntry = entries.FirstOrDefault();
if (firstEntry == null)
return;
var startScreenmanager = StartScreenManager.GetDefault();
var containsEntry = await startScreenmanager.ContainsAppListEntryAsync(firstEntry);
if (containsEntry)
return;
if (await startScreenmanager.RequestAddAppListEntryAsync(firstEntry))
{
_canExecute = false;
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
}
private async void Current_Activated(object sender, WindowActivatedEventArgs e)
{
var entries = await Package.Current.GetAppListEntriesAsync();
var firstEntry = entries.FirstOrDefault();
if (firstEntry == null)
{
_canExecute = false;
return;
}
if (ApiInformation.IsTypePresent("Windows.UI.StartScreen.StartScreenManager"))
{
var startScreenmanager = StartScreenManager.GetDefault();
_canExecute = !await startScreenmanager.ContainsAppListEntryAsync(firstEntry);
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
}
}
there is no way to pin the default live tile programmatically. you can only pin secondary tiles.
the default tile is always available programmatically but cannot be pinned by the app. only by the user itself from the list of apps.
your best solution is to create a secondary tile and ask to pin that. (would even be better to make the secondary tile to go to a specific area of the app as that is what secondary apps are for)
here is a guide on how to implement the secondary apps and how to pin them: https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh868249.aspx
I want to know what how to avoid or stop FileSystemWatcher raise event twice in C#? I have a solution that will detect everytime if there is newly created xml file from a folder. I test my application using creating xml file using notepad but from the listbox it displays twice.
How can I fix this issue?
Here is my code:
private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
try
{
fileSystemWatcher1.EnableRaisingEvents = false;
listBox1.Items.Add(e.FullPath);
}
finally
{
fileSystemWatcher1.EnableRaisingEvents = true;
}
}
private void button1_Click(object sender, EventArgs e)
{
DialogResult dialogSelectFolder = folderBrowserDialog1.ShowDialog();
if (dialogSelectFolder.ToString() == "OK")
{
textBox1.Text = folderBrowserDialog1.SelectedPath;
button2.Enabled = true;
}
}
private void button2_Click(object sender, EventArgs e)
{
button1.Enabled = false;
button2.Enabled = false;
button3.Enabled = true;
fileSystemWatcher1.EnableRaisingEvents = true;
fileSystemWatcher1.Path = textBox1.Text;
fileSystemWatcher1.Filter = "*.xml";
}
private void button3_Click(object sender, EventArgs e)
{
button1.Enabled = true;
button3.Enabled = false;
textBox1.Text = "";
fileSystemWatcher1.EnableRaisingEvents = false;
}
}
I have stumbled upon this problem myself twice and i created a class that helps you get only one event at a time. You might also get false events when the file is not in read mode (such as when you copy a file).
You have to create a queue and store all events there and if a time interval passes then raise the appropriate event.
Unfortunately this is not a simple function thus i will include complete code.
using System;
using System.IO;
using System.Timers;
using System.Collections;
using System.ComponentModel;
namespace menelabs.core
{
/// <summary>
/// This class wraps FileSystemEventArgs and RenamedEventArgs objects and detection of duplicate events.
/// </summary>
internal class DelayedEvent
{
private readonly FileSystemEventArgs _args;
/// <summary>
/// Only delayed events that are unique will be fired.
/// </summary>
private bool _delayed;
public DelayedEvent(FileSystemEventArgs args)
{
_delayed = false;
_args = args;
}
public FileSystemEventArgs Args
{
get
{
return _args;
}
}
public bool Delayed
{
get
{
return _delayed;
}
set
{
_delayed = value;
}
}
public virtual bool IsDuplicate(object obj)
{
DelayedEvent delayedEvent = obj as DelayedEvent;
if (delayedEvent == null)
return false; // this is not null so they are different
FileSystemEventArgs eO1 = _args;
RenamedEventArgs reO1 = _args as RenamedEventArgs;
FileSystemEventArgs eO2 = delayedEvent._args;
RenamedEventArgs reO2 = delayedEvent._args as RenamedEventArgs;
// The events are equal only if they are of the same type (reO1 and reO2
// are both null or NOT NULL) and have all properties equal.
// We also eliminate Changed events that follow recent Created events
// because many apps create new files by creating an empty file and then
// they update the file with the file content.
return ((eO1 != null && eO2 != null && eO1.ChangeType == eO2.ChangeType
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name) &&
((reO1 == null & reO2 == null) || (reO1 != null && reO2 != null &&
reO1.OldFullPath == reO2.OldFullPath && reO1.OldName == reO2.OldName))) ||
(eO1 != null && eO2 != null && eO1.ChangeType == WatcherChangeTypes.Created
&& eO2.ChangeType == WatcherChangeTypes.Changed
&& eO1.FullPath == eO2.FullPath && eO1.Name == eO2.Name);
}
}
/// <summary>
/// This class wraps a FileSystemWatcher object. The class is not derived
/// from FileSystemWatcher because most of the FileSystemWatcher methods
/// are not virtual. The class was designed to resemble FileSystemWatcher class
/// as much as possible so that you can use FileSystemSafeWatcher instead
/// of FileSystemWatcher objects.
/// FileSystemSafeWatcher will capture all events from the FileSystemWatcher object.
/// The captured events will be delayed by at least ConsolidationInterval milliseconds in order
/// to be able to eliminate duplicate events. When duplicate events are found, the last event
/// is droped and the first event is fired (the reverse is not recomended because it could
/// cause some events not be fired at all since the last event will become the first event and
/// it won't fire a if a new similar event arrives imediately afterwards).
/// </summary>
internal class FileSystemSafeWatcher
{
private readonly FileSystemWatcher _fileSystemWatcher;
/// <summary>
/// Lock order is _enterThread, _events.SyncRoot
/// </summary>
private readonly object _enterThread = new object(); // Only one timer event is processed at any given moment
private ArrayList _events;
private Timer _serverTimer;
private int _consolidationInterval = 1000; // milliseconds
#region Delegate to FileSystemWatcher
public FileSystemSafeWatcher()
{
_fileSystemWatcher = new FileSystemWatcher();
Initialize();
}
public FileSystemSafeWatcher(string path)
{
_fileSystemWatcher = new FileSystemWatcher(path);
Initialize();
}
public FileSystemSafeWatcher(string path, string filter)
{
_fileSystemWatcher = new FileSystemWatcher(path, filter);
Initialize();
}
/// <summary>
/// Gets or sets a value indicating whether the component is enabled.
/// </summary>
/// <value>true if the component is enabled; otherwise, false. The default is false. If you are using the component on a designer in Visual Studio 2005, the default is true.</value>
public bool EnableRaisingEvents
{
get
{
return _fileSystemWatcher.EnableRaisingEvents;
}
set
{
_fileSystemWatcher.EnableRaisingEvents = value;
if (value)
{
_serverTimer.Start();
}
else
{
_serverTimer.Stop();
_events.Clear();
}
}
}
/// <summary>
/// Gets or sets the filter string, used to determine what files are monitored in a directory.
/// </summary>
/// <value>The filter string. The default is "*.*" (Watches all files.)</value>
public string Filter
{
get
{
return _fileSystemWatcher.Filter;
}
set
{
_fileSystemWatcher.Filter = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether subdirectories within the specified path should be monitored.
/// </summary>
/// <value>true if you want to monitor subdirectories; otherwise, false. The default is false.</value>
public bool IncludeSubdirectories
{
get
{
return _fileSystemWatcher.IncludeSubdirectories;
}
set
{
_fileSystemWatcher.IncludeSubdirectories = value;
}
}
/// <summary>
/// Gets or sets the size of the internal buffer.
/// </summary>
/// <value>The internal buffer size. The default is 8192 (8K).</value>
public int InternalBufferSize
{
get
{
return _fileSystemWatcher.InternalBufferSize;
}
set
{
_fileSystemWatcher.InternalBufferSize = value;
}
}
/// <summary>
/// Gets or sets the type of changes to watch for.
/// </summary>
/// <value>One of the System.IO.NotifyFilters values. The default is the bitwise OR combination of LastWrite, FileName, and DirectoryName.</value>
/// <exception cref="System.ArgumentException">The value is not a valid bitwise OR combination of the System.IO.NotifyFilters values.</exception>
public NotifyFilters NotifyFilter
{
get
{
return _fileSystemWatcher.NotifyFilter;
}
set
{
_fileSystemWatcher.NotifyFilter = value;
}
}
/// <summary>
/// Gets or sets the path of the directory to watch.
/// </summary>
/// <value>The path to monitor. The default is an empty string ("").</value>
/// <exception cref="System.ArgumentException">The specified path contains wildcard characters.-or- The specified path contains invalid path characters.</exception>
public string Path
{
get
{
return _fileSystemWatcher.Path;
}
set
{
_fileSystemWatcher.Path = value;
}
}
/// <summary>
/// Gets or sets the object used to marshal the event handler calls issued as a result of a directory change.
/// </summary>
/// <value>The System.ComponentModel.ISynchronizeInvoke that represents the object used to marshal the event handler calls issued as a result of a directory change. The default is null.</value>
public ISynchronizeInvoke SynchronizingObject
{
get
{
return _fileSystemWatcher.SynchronizingObject;
}
set
{
_fileSystemWatcher.SynchronizingObject = value;
}
}
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is changed.
/// </summary>
public event FileSystemEventHandler Changed;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is created.
/// </summary>
public event FileSystemEventHandler Created;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is deleted.
/// </summary>
public event FileSystemEventHandler Deleted;
/// <summary>
/// Occurs when the internal buffer overflows.
/// </summary>
public event ErrorEventHandler Error;
/// <summary>
/// Occurs when a file or directory in the specified System.IO.FileSystemWatcher.Path is renamed.
/// </summary>
public event RenamedEventHandler Renamed;
/// <summary>
/// Begins the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void BeginInit()
{
_fileSystemWatcher.BeginInit();
}
/// <summary>
/// Releases the unmanaged resources used by the System.IO.FileSystemWatcher and optionally releases the managed resources.
/// </summary>
public void Dispose()
{
Uninitialize();
}
/// <summary>
/// Ends the initialization of a System.IO.FileSystemWatcher used on a form or used by another component. The initialization occurs at run time.
/// </summary>
public void EndInit()
{
_fileSystemWatcher.EndInit();
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Changed event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnChanged(FileSystemEventArgs e)
{
if (Changed != null)
Changed(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Created event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnCreated(FileSystemEventArgs e)
{
if (Created != null)
Created(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Deleted event.
/// </summary>
/// <param name="e">A System.IO.FileSystemEventArgs that contains the event data.</param>
protected void OnDeleted(FileSystemEventArgs e)
{
if (Deleted != null)
Deleted(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Error event.
/// </summary>
/// <param name="e">An System.IO.ErrorEventArgs that contains the event data.</param>
protected void OnError(ErrorEventArgs e)
{
if (Error != null)
Error(this, e);
}
/// <summary>
/// Raises the System.IO.FileSystemWatcher.Renamed event.
/// </summary>
/// <param name="e">A System.IO.RenamedEventArgs that contains the event data.</param>
protected void OnRenamed(RenamedEventArgs e)
{
if (Renamed != null)
Renamed(this, e);
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType)
{
//TODO
throw new NotImplementedException();
}
/// <summary>
/// A synchronous method that returns a structure that contains specific information on the change that occurred, given the type of change you want to monitor
/// and the time (in milliseconds) to wait before timing out.
/// </summary>
/// <param name="changeType">The System.IO.WatcherChangeTypes to watch for.</param>
/// <param name="timeout">The time (in milliseconds) to wait before timing out.</param>
/// <returns>A System.IO.WaitForChangedResult that contains specific information on the change that occurred.</returns>
public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int timeout)
{
//TODO
throw new NotImplementedException();
}
#endregion
#region Implementation
private void Initialize()
{
_events = ArrayList.Synchronized(new ArrayList(32));
_fileSystemWatcher.Changed += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Created += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Deleted += new FileSystemEventHandler(this.FileSystemEventHandler);
_fileSystemWatcher.Error += new ErrorEventHandler(this.ErrorEventHandler);
_fileSystemWatcher.Renamed += new RenamedEventHandler(this.RenamedEventHandler);
_serverTimer = new Timer(_consolidationInterval);
_serverTimer.Elapsed += new ElapsedEventHandler(this.ElapsedEventHandler);
_serverTimer.AutoReset = true;
_serverTimer.Enabled = _fileSystemWatcher.EnableRaisingEvents;
}
private void Uninitialize()
{
if (_fileSystemWatcher != null)
_fileSystemWatcher.Dispose();
if (_serverTimer != null)
_serverTimer.Dispose();
}
private void FileSystemEventHandler(object sender, FileSystemEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ErrorEventHandler(object sender, ErrorEventArgs e)
{
OnError(e);
}
private void RenamedEventHandler(object sender, RenamedEventArgs e)
{
_events.Add(new DelayedEvent(e));
}
private void ElapsedEventHandler(Object sender, ElapsedEventArgs e)
{
// We don't fire the events inside the lock. We will queue them here until
// the code exits the locks.
Queue eventsToBeFired = null;
if (System.Threading.Monitor.TryEnter(_enterThread))
{
// Only one thread at a time is processing the events
try
{
eventsToBeFired = new Queue(32);
// Lock the collection while processing the events
lock (_events.SyncRoot)
{
DelayedEvent current;
for (int i = 0; i < _events.Count; i++)
{
current = _events[i] as DelayedEvent;
if (current.Delayed)
{
// This event has been delayed already so we can fire it
// We just need to remove any duplicates
for (int j = i + 1; j < _events.Count; j++)
{
if (current.IsDuplicate(_events[j]))
{
// Removing later duplicates
_events.RemoveAt(j);
j--; // Don't skip next event
}
}
bool raiseEvent = true;
if (current.Args.ChangeType == WatcherChangeTypes.Created || current.Args.ChangeType == WatcherChangeTypes.Changed)
{
//check if the file has been completely copied (can be opened for read)
FileStream stream = null;
try
{
stream = File.Open(current.Args.FullPath, FileMode.Open, FileAccess.Read, FileShare.None);
// If this succeeds, the file is finished
}
catch (IOException)
{
raiseEvent = false;
}
finally
{
if (stream != null) stream.Close();
}
}
if (raiseEvent)
{
// Add the event to the list of events to be fired
eventsToBeFired.Enqueue(current);
// Remove it from the current list
_events.RemoveAt(i);
i--; // Don't skip next event
}
}
else
{
// This event was not delayed yet, so we will delay processing
// this event for at least one timer interval
current.Delayed = true;
}
}
}
}
finally
{
System.Threading.Monitor.Exit(_enterThread);
}
}
// else - this timer event was skipped, processing will happen during the next timer event
// Now fire all the events if any events are in eventsToBeFired
RaiseEvents(eventsToBeFired);
}
public int ConsolidationInterval
{
get
{
return _consolidationInterval;
}
set
{
_consolidationInterval = value;
_serverTimer.Interval = value;
}
}
protected void RaiseEvents(Queue deQueue)
{
if ((deQueue != null) && (deQueue.Count > 0))
{
DelayedEvent de;
while (deQueue.Count > 0)
{
de = deQueue.Dequeue() as DelayedEvent;
switch (de.Args.ChangeType)
{
case WatcherChangeTypes.Changed:
OnChanged(de.Args);
break;
case WatcherChangeTypes.Created:
OnCreated(de.Args);
break;
case WatcherChangeTypes.Deleted:
OnDeleted(de.Args);
break;
case WatcherChangeTypes.Renamed:
OnRenamed(de.Args as RenamedEventArgs);
break;
}
}
}
}
#endregion
}
}
You may find the code at:
https://github.com/melenaos/FileSystemSafeWatcher/blob/master/FileSystemSafeWatcher.cs