Ver Fonte

Merge pull request #1518 from AtomicGameEngine/JME-ATOMIC-LIFETIMEWORK

[C#] Object lifetime and other improvements
JoshEngebretson há 8 anos atrás
pai
commit
c0a700c2b4
32 ficheiros alterados com 1096 adições e 223 exclusões
  1. 23 2
      Script/AtomicNET/AtomicNET/Core/AtomicNET.cs
  2. 20 0
      Script/AtomicNET/AtomicNET/Core/IntPtrEqualityComparer.cs
  3. 87 107
      Script/AtomicNET/AtomicNET/Core/NativeCore.cs
  4. 131 4
      Script/AtomicNET/AtomicNET/Core/RefCounted.cs
  5. 156 0
      Script/AtomicNET/AtomicNET/Core/RefCountedCache.cs
  6. 61 0
      Script/AtomicNET/AtomicNET/Core/ReferenceHolder.cs
  7. 1 1
      Script/AtomicNET/AtomicNET/Physics/PhysicsWorld.cs
  8. 2 0
      Script/AtomicNET/AtomicNET/Player/Player.cs
  9. 10 4
      Script/AtomicNET/AtomicNET/Scene/CSComponent.cs
  10. 40 24
      Script/AtomicNET/AtomicNET/Scene/Node.cs
  11. 52 9
      Script/AtomicNET/AtomicNET/Scene/Scene.cs
  12. 4 1
      Script/Packages/Atomic/Container.json
  13. 8 1
      Source/Atomic/Graphics/RenderSurface.cpp
  14. 8 1
      Source/Atomic/Graphics/RenderSurface.h
  15. 6 4
      Source/Atomic/Math/StringHash.cpp
  16. 2 2
      Source/Atomic/Math/StringHash.h
  17. 227 23
      Source/Atomic/Metrics/Metrics.cpp
  18. 38 2
      Source/Atomic/Metrics/Metrics.h
  19. 26 0
      Source/Atomic/Resource/ResourceCache.cpp
  20. 3 0
      Source/Atomic/Resource/ResourceCache.h
  21. 2 0
      Source/Atomic/Script/ScriptVariant.cpp
  22. 24 21
      Source/Atomic/UI/SystemUI/DebugHud.cpp
  23. 49 1
      Source/AtomicNET/NETNative/NETCInterop.cpp
  24. 24 0
      Source/AtomicNET/NETNative/NETCore.cpp
  25. 8 0
      Source/AtomicNET/NETNative/NETCore.h
  26. 4 0
      Source/ToolCore/Command/BindCmd.cpp
  27. 20 12
      Source/ToolCore/JSBind/CSharp/CSClassWriter.cpp
  28. 14 3
      Source/ToolCore/JSBind/CSharp/CSFunctionWriter.cpp
  29. 7 0
      Source/ToolCore/JSBind/JSBClass.h
  30. 35 0
      Source/ToolCore/JSBind/JSBModule.cpp
  31. 3 0
      Source/ToolCore/JSBind/JSBModule.h
  32. 1 1
      Submodules/AtomicExamples

+ 23 - 2
Script/AtomicNET/AtomicNET/Core/AtomicNET.cs

@@ -59,6 +59,22 @@ namespace AtomicEngine
             return csi_Atomic_AtomicNET_StringToStringHash(value);
         }
 
+        /// <summary>
+        ///  Runs a GC collection and waits for any finalizers
+        /// </summary>
+        public static void RunGC()
+        {
+            NativeCore.RunGC();
+        }
+
+        /// <summary>
+        ///  Returns true if called on main engine thread, false if on another thread
+        /// </summary>
+        public static bool IsMainThread()
+        {
+            return csi_AtomicEngine_IsMainThread();
+        }
+
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         private static extern uint csi_Atomic_AtomicNET_StringToStringHash(string name);
 
@@ -66,6 +82,7 @@ namespace AtomicEngine
         private static EventDispatchDelegate eventDispatchDelegate = NativeCore.EventDispatch;
         private static UpdateDispatchDelegate updateDispatchDelegate = NativeCore.UpdateDispatch;
         private static RefCountedDeletedDelegate refCountedDeletedDelegate = NativeCore.RefCountedDeleted;
+        private static ThrowManagedExceptionDelegate throwManagedExceptionDelegate = NativeCore.ThrowManagedException;
 
         public static void Initialize()
         {
@@ -99,7 +116,7 @@ namespace AtomicEngine
 
             PlayerModule.Initialize();
 
-            IntPtr coreptr = csi_Atomic_NETCore_Initialize(eventDispatchDelegate, updateDispatchDelegate, refCountedDeletedDelegate);
+            IntPtr coreptr = csi_Atomic_NETCore_Initialize(eventDispatchDelegate, updateDispatchDelegate, refCountedDeletedDelegate, throwManagedExceptionDelegate);
 
             NETCore core = (coreptr == IntPtr.Zero ? null : NativeCore.WrapNative<NETCore>(coreptr));
 
@@ -120,7 +137,11 @@ namespace AtomicEngine
         }
 
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern IntPtr csi_Atomic_NETCore_Initialize(EventDispatchDelegate eventDispatch, UpdateDispatchDelegate updateDispatch, RefCountedDeletedDelegate refCountedDeleted);
+        private static extern IntPtr csi_Atomic_NETCore_Initialize(EventDispatchDelegate eventDispatch, UpdateDispatchDelegate updateDispatch, RefCountedDeletedDelegate refCountedDeleted, ThrowManagedExceptionDelegate throwManagedException);
+
+        [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [return: MarshalAs(UnmanagedType.I1)]
+        private static extern bool csi_AtomicEngine_IsMainThread();
 
         private static Context context;
         private static Dictionary<Type, AObject> subSystems = new Dictionary<Type, AObject>();

+ 20 - 0
Script/AtomicNET/AtomicNET/Core/IntPtrEqualityComparer.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace AtomicEngine
+{
+    internal class IntPtrEqualityComparer : IEqualityComparer<IntPtr>
+    {
+        public static readonly IEqualityComparer<IntPtr> Instance = new IntPtrEqualityComparer();
+
+        public bool Equals(IntPtr x, IntPtr y)
+        {
+            return x == y;
+        }
+
+        public int GetHashCode(IntPtr obj)
+        {
+            return obj.GetHashCode();
+        }
+    }
+}

+ 87 - 107
Script/AtomicNET/AtomicNET/Core/NativeCore.cs

@@ -21,6 +21,9 @@ namespace AtomicEngine
     [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
     public delegate void RefCountedDeletedDelegate(IntPtr refCounted);
 
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+    public delegate void ThrowManagedExceptionDelegate(string errorMsg);
+
     public class NativeType
     {
 
@@ -41,7 +44,10 @@ namespace AtomicEngine
 
     }
 
-    public static class NativeCore
+    /// <summary>
+    ///  Internal class for native interop 
+    /// </summary>
+    internal static class NativeCore
     {
 
         static internal void SubscribeToEvent(AObject receiver, uint eventType)
@@ -69,12 +75,6 @@ namespace AtomicEngine
                     return;
             }
 
-            WeakReference<RefCounted> w = null;
-            if (!nativeLookup.TryGetValue(receiver.nativeInstance, out w))
-            {
-                throw new InvalidOperationException("NativeCore.SubscribeToEvent - unable to find native receiver instance");
-            }
-
             eventReceivers.Add(new EventSubscription(receiver, sender));
         }
 
@@ -113,11 +113,15 @@ namespace AtomicEngine
 
         static internal void RemoveEventSender(IntPtr sender)
         {
-            //TODO: OPTIMIZE
-
             if (sender == IntPtr.Zero)
                 return;
 
+            var refCounted = refCountedCache.Get(sender)?.Reference;
+
+            // If we're no longer registered or not an Object, early out
+            if (refCounted == null || !refCounted.IsObject())
+                return;
+
             foreach (var subList in eventReceiverLookup.Values)
             {
                 var len = subList.Count;
@@ -140,30 +144,14 @@ namespace AtomicEngine
                 svm[i] = new ScriptVariantMap();
         }
 
-
-        static float expireDelta = 0.0f;
-
         // called ahead of E_UPDATE event
         #if ATOMIC_IOS
         [MonoPInvokeCallback(typeof(UpdateDispatchDelegate))]
         #endif
         public static void UpdateDispatch(float timeStep)
         {
-            expireDelta += timeStep;
-            if (expireDelta > 2.0f)
-            {
-                expireDelta = 0.0f;
-
-
-                // TODO: tune GC
-                /*
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-                GC.Collect();
-                */
 
-                ExpireNatives();
-            }
+            RefCounted.ReleaseFinalized();
         }
 
         #if ATOMIC_IOS
@@ -231,8 +219,15 @@ namespace AtomicEngine
         /// </summary>
         public static void RunGC()
         {
+            // run a GC collection
             GC.Collect();
+            // finalizers can run on any thread, we're explicitly running a GC here
+            // so wait for all the finalizers to finish            
             GC.WaitForPendingFinalizers();
+            // Anything finalized on another thread will now be available to release 
+            // in main thread
+            RefCounted.ReleaseFinalized();
+
             ExpireNatives();
         }
 
@@ -267,31 +262,6 @@ namespace AtomicEngine
                     break;
             }
 
-            RefCounted r;
-
-            foreach (var native in nativeLookup.Keys.ToList())
-            {
-                var w = nativeLookup[native];
-
-                if (!w.TryGetTarget(out r))
-                {
-                    // expired
-                    csi_AtomicEngine_ReleaseRef(native);
-                    nativeLookup.Remove(native);
-                    //nativesRemoved++;
-                }
-
-                if (watch.ElapsedMilliseconds > 16)
-                    break;
-            }
-
-            /*
-            if (nativesRemoved != 0 || eventListenersRemoved != 0)
-            {
-                Console.WriteLine("Released {0} natives and {1} event receivers", nativesRemoved, eventListenersRemoved);
-            }
-            */
-
         }
 
         // Called from RefCounted native destructor, refCounted is not valid for any operations here
@@ -300,29 +270,56 @@ namespace AtomicEngine
         #endif
         public static void RefCountedDeleted(IntPtr refCounted)
         {
-            nativeLookup.Remove(refCounted);
+            
+        }
+
+        // Called to throw a managed exception from native code
+#if ATOMIC_IOS
+        [MonoPInvokeCallback(typeof(RefCountedDeletedDelegate))]
+#endif
+        public static void ThrowManagedException(string errorMsg)
+        {
+            throw new InvalidOperationException("Native Exception: " + errorMsg);
+        }
+
+        internal static void RemoveNative(IntPtr refCounted)
+        {
+            if (refCounted == IntPtr.Zero)
+                return;
+           
             RemoveEventSender(refCounted);
+
+            refCountedCache.Remove(refCounted);
+
         }
 
         // register a newly created native
-        public static IntPtr RegisterNative(IntPtr native, RefCounted r)
+        public static IntPtr RegisterNative(IntPtr native, RefCounted r, InstantiationType instantiationType = InstantiationType.INSTANTIATION_NET)
         {
-            if (nativeLookup.ContainsKey(native))
+            if (native == IntPtr.Zero || r == null)
             {
-                throw new InvalidOperationException("NativeCore.RegisterNative - Duplicate IntPtr key");
+                throw new InvalidOperationException("NativeCore.RegisterNative - native == IntPtr.Zero || RefCounted instance == null");
             }
 
-            r.nativeInstance = native;
-            // keep native side alive
-            r.AddRef();
+            if (instantiationType == InstantiationType.INSTANTIATION_NET)
+            {
+                if (r.nativeInstance != IntPtr.Zero)
+                {
+                    throw new InvalidOperationException("NativeCore.RegisterNative - NET Instantiated RefCounted with initialized nativeInstance");
+                }
+
+                r.nativeInstance = native;
+            }
 
-            nativeLookup[native] = new WeakReference<RefCounted>(r);
+            r.InstantiationType = instantiationType;
+            r.InternalInit();
 
-            r.InstantiationType = InstantiationType.INSTANTIATION_NET;
+            refCountedCache.Add(r);
 
             r.PostNativeUpdate();
 
             return native;
+
         }
 
 
@@ -330,35 +327,15 @@ namespace AtomicEngine
         public static T WrapNative<T>(IntPtr native) where T : RefCounted
         {
             if (native == IntPtr.Zero)
-                return null;
-
-            RefCounted r;
-            WeakReference<RefCounted> w;
-
-            // first see if we're already available
-            if (nativeLookup.TryGetValue(native, out w))
             {
+                throw new InvalidOperationException("NativeCore.WrapNative - Attempting to wrap native instance IntPtr.Zero");
+            }
 
-                if (w.TryGetTarget(out r))
-                {
-                    // we're alive!
-                    return (T)r;
-                }
-                else
-                {
-                    // we were seen before, but have since been GC'd, remove!
-                    nativeLookup.Remove(native);
-
-                    if (csi_Atomic_RefCounted_Refs(native) == 1)
-                    {
-                        // only managed ref remains, so release and return null
-                        csi_AtomicEngine_ReleaseRef(native);
-                        return null;
-                    }
+            var reference = refCountedCache.Get(native)?.Reference;
 
-                    csi_AtomicEngine_ReleaseRef(native);
-                }
-            }
+            // This isn't really a good test to verify right object, better to test if not a T and error?
+            if (reference is T)
+                return (T)reference;
 
             IntPtr classID = RefCounted.csi_Atomic_RefCounted_GetClassID(native);
 
@@ -378,27 +355,20 @@ namespace AtomicEngine
             if (nativeType.Type == typeof(CSComponent))
                 return null;
 
-            // and store, with downcast support for instance Component -> StaticModel
-            // we never want to hit this path for script inherited natives
-
-            r = nativeType.managedConstructor(native);
-
-            w = new WeakReference<RefCounted>(r);
-            NativeCore.nativeLookup[native] = w;
+            // Construct managed instance wrapper for native instance
+            // this has downcast support for instance Component -> StaticModel
+            // note, we never want to hit this path for script inherited natives
 
-            // store a ref, so native side will not be released while we still have a reference in managed code
-            r.AddRef();
+            var r = nativeType.managedConstructor(native);
 
-            // Note: r.InstantiationType may be INSTANTIATION_NET here is we were GC'd, native still had a reference, and we came back
-            if (r.InstantiationType == InstantiationType.INSTANTIATION_NET)
-            {
-                //Log.Warn($"Wrapped {r.GetType().Name} was originally instantiated in NET, changing to native, this is likely an error");
-                //r.InstantiationType = InstantiationType.INSTANTIATION_NATIVE;
-            }
-
-            r.PostNativeUpdate();
+            // IMPORTANT: if a RefCounted instance is created in managed code, has reference count increased in native code
+            // and managed side is GC'd, the original NET created instance will be gone and we can get it back here reported
+            // as instantiated in native code.  May want a transative boolean to be able to tell when an object has passed this "barrier"
+            // which is somewhat common
+            RegisterNative(native, r, InstantiationType.INSTANTIATION_NATIVE);
 
             return (T)r;
+
         }
 
         internal static string GetNativeTypeName(IntPtr native)
@@ -410,7 +380,7 @@ namespace AtomicEngine
         private static extern IntPtr csi_Atomic_RefCounted_GetTypeName(IntPtr self);
 
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
+        internal static extern int csi_Atomic_RefCounted_Refs(IntPtr self);
 
         public static void RegisterNativeType(NativeType nativeType)
         {
@@ -447,8 +417,7 @@ namespace AtomicEngine
             return ancestorType;
         }
 
-        // weak references here, hold a ref native side
-        internal static Dictionary<IntPtr, WeakReference<RefCounted>> nativeLookup = new Dictionary<IntPtr, WeakReference<RefCounted>>();
+        private static RefCountedCache refCountedCache = new RefCountedCache();
 
         // weak references here, hold a ref native side
         internal static Dictionary<uint, List<EventSubscription>> eventReceiverLookup = new Dictionary<uint, List<EventSubscription>>();
@@ -461,8 +430,19 @@ namespace AtomicEngine
         // Managed Type to NativeType lookup
         internal static Dictionary<Type, NativeType> typeToNativeType = new Dictionary<Type, NativeType>();
 
+        // Access to native reference counting not needing a managed RefCounted instance
+
+        [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        internal static extern void csi_AtomicEngine_AddRef(IntPtr refCounted);
+
+        [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        internal static extern void csi_AtomicEngine_AddRefSilent(IntPtr refCounted);
+
+        [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        internal static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
+
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern void csi_AtomicEngine_ReleaseRef(IntPtr refCounted);
+        internal static extern void csi_AtomicEngine_ReleaseSilent(IntPtr refCounted);
 
         internal struct EventSubscription
         {

+ 131 - 4
Script/AtomicNET/AtomicNET/Core/RefCounted.cs

@@ -1,15 +1,83 @@
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 
 namespace AtomicEngine
 {
 
+    /// <summary>
+    ///  Class which ensures Disposed is called, without needing to implement Dispose in class Finalizer
+    /// </summary>
+    internal class RefCountedSafeFileHandle : SafeHandle
+    {
+        public RefCountedSafeFileHandle(IntPtr handle, bool ownsHandle = true)
+            : base(handle, ownsHandle)
+        {
+            if (handle == IntPtr.Zero)
+            {
+                throw new InvalidOperationException("RefCountedSafeFileHandle - native == IntPtr.Zero");
+            }
+
+            NativeCore.csi_AtomicEngine_AddRef(handle);
+        }
+
+        override public bool IsInvalid { get { return handle == IntPtr.Zero; } }
+
+        /// <summary>
+        ///  Release the handle, which will release the native instance immediately if in main thread
+        ///  otherwise, will queue
+        /// </summary>
+        override protected bool ReleaseHandle()
+        {
+            if (handle == IntPtr.Zero)
+            {
+                throw new InvalidOperationException("RefCountedSafeFileHandle.ReleaseHandle - native == IntPtr.Zero");
+            }
+
+            // We can be called from Dispose in main thread or from finalizers, which aren't in the main thread
+            if (AtomicNET.IsMainThread())
+            {
+                NativeCore.csi_AtomicEngine_ReleaseRef(handle);
+            }
+            else
+            {
+                // We're in a finalizer, need to add to queue to release when
+                // back in main thread
+                lock (RefCounted.refCountedFinalizerQueue)
+                {
+                    RefCounted.refCountedFinalizerQueue.Add(handle);
+                }
+
+            }
+
+            handle = IntPtr.Zero;
+
+            return true;
+        }
+    }
+
     [ComVisible(true)]
-    public partial class RefCounted
+    public partial class RefCounted : IDisposable
     {
+        /// <summary>
+        ///  If instance has been disposed, native object is in an undefined state, and instance should not be accessed
+        /// </summary>
+        public bool Disposed => disposed;
+
+        // _handle is set to null to indicate disposal of this instance.
+        private RefCountedSafeFileHandle refHandle;
 
         public RefCounted()
         {
+
+        }
+
+        /// <summary>
+        ///  WARNING: C# finalizers can be called in any thread!!!
+        ///  Don't need native cleanup code in the finalizer as use SafeHandle
+        /// </summary>
+        ~RefCounted()
+        {
         }
 
         protected RefCounted(IntPtr native)
@@ -17,9 +85,66 @@ namespace AtomicEngine
             nativeInstance = native;
         }
 
-        // This method may be called multiple times, called on instance after it is either registered as a new native created in C# (InstantiationType == InstantiationType.INSTANTIATION_NET)
-        // or a native which has been wrapped ((InstantiationType != InstantiationType.INSTANTIATION_NET)
-        // Note that RefCounted that get GC'd from script, can still live in native code, and get rewrapped 
+        internal void InternalInit()
+        {   
+            if (refHandle != null)
+                throw new InvalidOperationException("RefCounted.Init - refHandle already initialized");
+
+            refHandle = new RefCountedSafeFileHandle(nativeInstance);
+        }
+
+        /// <summary>
+        ///  Dispose method of IDisposible interface, note that native code can hold references to 
+        ///  RefCounted derived instances, disposing RefCounted instances from managed code releases
+        ///  the managed reference and will only release the native RefCounted instance if no other references 
+        ///  are held in native code. 
+        /// </summary>
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            disposed = true;
+
+            if (refHandle != null && !refHandle.IsInvalid)
+            {
+                NativeCore.RemoveNative(nativeInstance);
+
+                // Free the handle
+                refHandle.Dispose();                
+            }
+
+            nativeInstance = IntPtr.Zero;
+
+        }
+
+        static internal List<IntPtr> refCountedFinalizerQueue = new List<IntPtr>();
+
+        /// <summary>
+        /// Releases RefCounted instances which were finalized (which can happen on any thread)
+        /// </summary>
+        static internal void ReleaseFinalized()
+        {
+            lock (refCountedFinalizerQueue)
+            {
+                foreach (var native in refCountedFinalizerQueue)
+                {
+                    NativeCore.RemoveNative(native);
+                    NativeCore.csi_AtomicEngine_ReleaseRef(native);
+                }
+
+                refCountedFinalizerQueue.Clear();
+            }
+
+        }
+
+        /// <summary>
+        /// This method may be called multiple times, called on instance after it is either registered as a new native created in C# (InstantiationType == InstantiationType.INSTANTIATION_NET)
+        /// or a native which has been wrapped ((InstantiationType != InstantiationType.INSTANTIATION_NET)
+        /// Note that RefCounted that get GC'd from script, can still live in native code, and get rewrapped 
+        /// </summary>
         internal virtual void PostNativeUpdate()
         {
 
@@ -37,6 +162,8 @@ namespace AtomicEngine
 
         public IntPtr nativeInstance = IntPtr.Zero;
 
+        private bool disposed = false;
+
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
         public static extern IntPtr csi_Atomic_RefCounted_GetClassID(IntPtr self);
 

+ 156 - 0
Script/AtomicNET/AtomicNET/Core/RefCountedCache.cs

@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace AtomicEngine
+{
+
+    /// <summary>
+    /// Holds managed instance wrappers of native RefCounted instances
+    /// </summary>
+    internal class RefCountedCache
+    {
+
+        internal enum RefType
+        {
+            REF_DEFAULT = 0,
+            REF_WEAK = 1,
+            REF_STRONG = 2
+        }
+
+        static Dictionary<IntPtr, ReferenceHolder<RefCounted>> knownObjects =
+            new Dictionary<IntPtr, ReferenceHolder<RefCounted>>(IntPtrEqualityComparer.Instance);
+
+        public int Count => knownObjects.Count;
+
+        public string GetCacheStatus()
+        {
+            lock (knownObjects)
+            {
+                var topMcw = knownObjects
+                    .Select(t => t.Value.Reference?.GetType())
+                    .Where(t => t != null)
+                    .GroupBy(k => k.Name)
+                    .OrderByDescending(t => t.Count())
+                    .Take(10)
+                    .Select(t => $"{t.Key}:  {t.Count()}");
+                return $"Size: {Count}\nTypes: {string.Join("\n", topMcw)}";
+            }
+        }
+
+        public void Add(RefCounted refCounted, RefType refType = RefType.REF_DEFAULT)
+        {
+            lock (knownObjects)
+            {
+                ReferenceHolder<RefCounted> knownObject;
+                if (knownObjects.TryGetValue(refCounted.nativeInstance, out knownObject))
+                {
+                    var existingObj = knownObject?.Reference;
+
+                    // this is another check verifying correct RefCounted by using type, which isn't a good test
+                    if (existingObj != null && !IsInHierarchy(existingObj.GetType(), refCounted.GetType()))
+                        throw new InvalidOperationException($"NativeInstance '{refCounted.nativeInstance}' is in use by '{existingObj.GetType().Name}' (IsDeleted={existingObj.nativeInstance == IntPtr.Zero}). {refCounted.GetType()}");
+                }
+
+                // first check if explicit strong reference
+                bool strongRef = refType == RefType.REF_STRONG;
+
+                if (!strongRef)
+                {
+                    strongRef = StrongRefByDefault(refCounted);
+                }
+
+                knownObjects[refCounted.nativeInstance] = new ReferenceHolder<RefCounted>(refCounted, !strongRef);
+            }
+        }
+
+        public bool Remove(IntPtr ptr)
+        {
+            lock (knownObjects)
+            {
+                return knownObjects.Remove(ptr);
+            }
+        }
+
+        public ReferenceHolder<RefCounted> Get(IntPtr ptr)
+        {
+            lock (knownObjects)
+            {
+                ReferenceHolder<RefCounted> refCounted;
+                knownObjects.TryGetValue(ptr, out refCounted);
+                return refCounted;
+            }
+        }
+
+        public void Clean()
+        {
+            IntPtr[] handles;
+
+            lock (knownObjects)
+                handles = knownObjects.OrderBy(t => GetDisposePriority(t.Value)).Select(t => t.Key).ToArray();
+
+            foreach (var handle in handles)
+            {
+                ReferenceHolder<RefCounted> refHolder;
+                lock (knownObjects)
+                    knownObjects.TryGetValue(handle, out refHolder);
+                refHolder?.Reference?.Dispose();
+            }
+
+            Log.Warn($"RefCountedCache objects alive: {knownObjects.Count}");
+            
+        }
+
+        int GetDisposePriority(ReferenceHolder<RefCounted> refHolder)
+        {
+            const int defaultPriority = 1000;
+            var obj = refHolder?.Reference;
+            if (obj == null)
+                return defaultPriority;
+            if (obj is Scene)
+                return 1;
+            if (obj is Context)
+                return int.MaxValue;
+            //TODO:
+            return defaultPriority;
+        }
+
+        /// <summary>
+        /// Dispose a list of RefCounted instances
+        /// </summary>
+        internal static void Dispose(List<RefCounted> disposeList)
+        {
+            lock (knownObjects)
+            {
+
+                foreach (var refCounted in disposeList)
+                {
+                    knownObjects.Remove(refCounted);
+                    refCounted.Dispose();
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// Some types are stored with a StrongRef by default, to help avoid Object churn and support explicit Disposing
+        /// </summary>
+        bool StrongRefByDefault(RefCounted refCounted)
+        {
+            if (refCounted is Scene) return true;
+            if (refCounted is Node) return true;
+            if (refCounted is Context) return true;
+            if (refCounted is Component) return true;
+            return false;
+        }
+
+        bool IsInHierarchy(Type t1, Type t2)
+        {
+            if (t1 == t2) return true;
+            if (t1.GetTypeInfo().IsSubclassOf(t2)) return true;
+            if (t2.GetTypeInfo().IsSubclassOf(t1)) return true;
+            return false;
+        }
+    }
+}

+ 61 - 0
Script/AtomicNET/AtomicNET/Core/ReferenceHolder.cs

@@ -0,0 +1,61 @@
+using System;
+
+namespace AtomicEngine
+{
+    internal class ReferenceHolder<T> where T : class
+    {
+        public ReferenceHolder(T obj, bool weak)
+        {
+            if (weak)
+                WeakRef = new WeakReference<T>(obj);
+            else
+                StrongRef = obj;
+        }
+
+        public T StrongRef { get; private set; }
+        public WeakReference<T> WeakRef { get; private set; }
+        public bool IsWeak => WeakRef != null;
+
+        public T Reference
+        {
+            get
+            {
+                if (StrongRef != null)
+                    return StrongRef;
+
+                T wr;
+                WeakRef.TryGetTarget(out wr);
+                return wr;
+            }
+        }
+
+        /// <summary>
+        /// Change Weak to Strong
+        /// </summary>
+        public bool MakeStrong()
+        {
+            if (StrongRef != null)
+                return true;
+            T strong = null;
+            WeakRef?.TryGetTarget(out strong);
+
+            StrongRef = strong;
+            WeakRef = null;
+            return StrongRef != null;
+        }
+
+        /// <summary>
+        /// Change Strong to Weak
+        /// </summary>
+        public bool MakeWeak()
+        {
+            if (StrongRef != null)
+            {
+                WeakRef = new WeakReference<T>(StrongRef);
+                StrongRef = null;
+                return true;
+            }
+            return false;
+        }
+    }
+}

+ 1 - 1
Script/AtomicNET/AtomicNET/Physics/PhysicsWorld.cs

@@ -13,7 +13,7 @@ namespace AtomicEngine
         public float HitFraction;
 
         IntPtr bodyPtr;
-        public RigidBody Body => NativeCore.WrapNative<RigidBody>(bodyPtr);
+        public RigidBody Body => bodyPtr == IntPtr.Zero ? null : NativeCore.WrapNative<RigidBody>(bodyPtr);
     }
 
     public partial class PhysicsWorld : Component

+ 2 - 0
Script/AtomicNET/AtomicNET/Player/Player.cs

@@ -28,6 +28,8 @@ namespace AtomicPlayer
             {
                 if (loadedScenes.Contains(e.Scene))
                     loadedScenes.Remove(e.Scene);
+
+                e.Scene.Dispose();
             });
 
         }

+ 10 - 4
Script/AtomicNET/AtomicNET/Scene/CSComponent.cs

@@ -29,22 +29,28 @@ namespace AtomicEngine
                 throw new InvalidOperationException($"CSComponent() - Recursive CSComponent instantiation in default constructor during load type: { GetType().Name} ");
             }
 
+            // detect instantiation type
+            InstantiationType itype = InstantiationType.INSTANTIATION_NATIVE;
+
+            IntPtr ninstance = IntPtr.Zero;
+
             if (nativeLoadOverride == IntPtr.Zero)
             {
-                // We are being "new'd" in script
-                nativeInstance = csi_Atomic_CSComponent_Constructor();                
+                // We are being "new'd" in managed code
+                ninstance = csi_Atomic_CSComponent_Constructor();
+                itype = InstantiationType.INSTANTIATION_NET;
             }
             else
             {
                 // We are loading from a serialized CSComponent
-                nativeInstance = nativeLoadOverride;
+                ninstance = nativeInstance = nativeLoadOverride;
 
                 // validation bookkeeping
                 nativeLoadOverrideValidate = nativeLoadOverride;
                 nativeLoadOverride = IntPtr.Zero;
             }
 
-            NativeCore.RegisterNative(nativeInstance, this);
+            NativeCore.RegisterNative(ninstance, this, itype);
 
 
         }

+ 40 - 24
Script/AtomicNET/AtomicNET/Scene/Node.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Runtime.InteropServices;
 using static System.Reflection.IntrospectionExtensions;
 
 namespace AtomicEngine
@@ -92,62 +90,80 @@ namespace AtomicEngine
 
         public void GetComponents<T>(Vector<T> dest, bool recursive = false) where T : Component
         {
-            Vector<Component> components = new Vector<Component>();
-            GetComponents(components, typeof(T).Name, recursive);
-            for (int i = 0; i < components.Size; i++)
+            dest.Clear();
+            GetComponents(ComponentVector, typeof(T).Name, recursive);
+            for (int i = 0; i < ComponentVector.Size; i++)
             {
-                dest.Push((T)components[i]);
+                if (ComponentVector[i] != null)
+                    dest.Push((T)ComponentVector[i]);
             }
+            ComponentVector.Clear();
         }
 
         public void GetDerivedComponents<T>(Vector<T> dest, bool recursive = false) where T : Component
         {
-            Vector<Component> components = new Vector<Component>();
-            GetComponents(components, recursive);
-            for (int i = 0; i < components.Size; ++i)
+            dest.Clear();
+            GetComponents(ComponentVector, typeof(Component).Name, recursive);
+            for (int i = 0; i < ComponentVector.Size; ++i)
             {
-                T t = components[i] as T;
+                T t = ComponentVector[i] as T;
                 if (t != null)
                     dest.Push(t);
             }
+            ComponentVector.Clear();
         }
 
         public T GetCSComponent<T>(bool recursive = false) where T : CSComponent
         {
-            Vector<Component> components = new Vector<Component>();
-            GetComponents(components, nameof(CSComponent), recursive);
-            for (int i = 0; i < components.Size; i++)
+            GetComponents(ComponentVector, nameof(CSComponent), recursive);
+            for (int i = 0; i < ComponentVector.Size; i++)
             {
-                if (components[i].GetType() == typeof(T))
-                    return (T) components[i];
+                Component component = ComponentVector[i];
+                if (component != null &&
+                    component.GetType() == typeof(T))
+                {
+                    ComponentVector.Clear();
+                    return (T)component;
+                }
             }
 
+            ComponentVector.Clear();
             return null;
         }
 
         public void GetCSComponents<T>(Vector<T> dest, bool recursive = false) where T : CSComponent
         {
-            Vector<Component> components = new Vector<Component>();
-            GetComponents(components, nameof(CSComponent), recursive);
-            for (int i = 0; i < components.Size; i++)
+            dest.Clear();
+            GetComponents(ComponentVector, nameof(CSComponent), recursive);
+            for (int i = 0; i < ComponentVector.Size; i++)
             {
-                Component component = components[i];
-                if (component.GetType() == typeof(T))
+                Component component = ComponentVector[i];
+                if (component != null &&
+                    component.GetType() == typeof(T))
                     dest.Push((T)component);
             }
+            ComponentVector.Clear();
         }
 
         public void GetDerivedCSComponents<T>(Vector<T> dest, bool recursive = false) where T : CSComponent
         {
-            Vector<Component> components = new Vector<Component>();
-            GetComponents(components, nameof(CSComponent), recursive);
-            for (int i = 0; i < components.Size; ++i)
+            dest.Clear();
+            GetComponents(ComponentVector, nameof(CSComponent), recursive);
+            for (int i = 0; i < ComponentVector.Size; ++i)
             {
-                T t = components[i] as T;
+                T t = ComponentVector[i] as T;
                 if (t != null)
                     dest.Push(t);
             }
+            ComponentVector.Clear();
         }
+
+        // Reuse vectors to avoid churn, but don't have one on every referenced node
+        // Wrapping in a static property, instead of just immediate static allocation,
+        // Because Runtime isn't ready at time of static initialization: 
+        // https://github.com/AtomicGameEngine/AtomicGameEngine/issues/1512
+        private static Vector<Component> ComponentVector => lazyComponentVector.Value;
+        private static Lazy<Vector<Component>> lazyComponentVector = new Lazy<Vector<Component>>();
     }
 
 }

+ 52 - 9
Script/AtomicNET/AtomicNET/Scene/Scene.cs

@@ -8,6 +8,49 @@ namespace AtomicEngine
 {
     public partial class Scene : Node
     {
+        override protected void Dispose(bool disposing)
+        {
+            UnsubscribeFromAllEvents();
+
+            if (disposing)
+            {
+                // list of nodes/components to dispose
+                var disposeList = new List<RefCounted>();
+
+                // IMPORTANT: Care must be taken to clear these vectors
+                // otherwise, references will be held until the Vector is GC'd
+                // and the child nodes/components/resources will not be immediately disposed
+                var components = new Vector<Component>();
+                var nodes = new Vector<Node>();
+
+                // Get scene components and add to dispose list
+                GetComponents(components);
+                disposeList.AddRange(components);
+                components.Clear();
+
+                // get all children of scene and add their components to the dispose list
+                GetChildren(nodes, true);                
+                foreach (var node in nodes)
+                {
+                    node.GetComponents(components);
+                    disposeList.AddRange(components);
+                    components.Clear();
+                }
+
+                // add nodes to the back of the list
+                disposeList.AddRange(nodes);
+
+                nodes.Clear();
+
+                // dispose of list
+                RefCountedCache.Dispose(disposeList);                
+            }
+                            
+            // dispose ourself
+            base.Dispose(disposing);            
+
+        }
+
         internal override void PostNativeUpdate()
         {    
             SubscribeToEvent<NodeAddedEvent>(this, e =>
@@ -16,14 +59,14 @@ namespace AtomicEngine
 
                 // The NodeAdded event is generated when adding a node as a child
 
-                e.Node.GetComponents<CSComponent>(componentVector);
+                e.Node.GetComponents<CSComponent>(csComponentVector);
 
-                for (uint i = 0; i < componentVector.Size; i++)
+                for (uint i = 0; i < csComponentVector.Size; i++)
                 {
-                    AddCSComponent(componentVector[i]);
+                    AddCSComponent(csComponentVector[i]);
                 }
 
-                componentVector.Clear();
+                csComponentVector.Clear();
 
             });
 
@@ -34,14 +77,14 @@ namespace AtomicEngine
                 // The NodeRemoved event is generated when explicitly removing nodes from a scene
                 // For general cleanup, it will not be generated
                 
-                e.Node.GetComponents<CSComponent>(componentVector);
+                e.Node.GetComponents<CSComponent>(csComponentVector);
 
-                for (uint i = 0; i < componentVector.Size; i++)
+                for (uint i = 0; i < csComponentVector.Size; i++)
                 {
-                    HandleComponentRemoved(componentVector[i]);
+                    HandleComponentRemoved(csComponentVector[i]);
                 }
 
-                componentVector.Clear();
+                csComponentVector.Clear();
 
             });
 
@@ -372,7 +415,7 @@ namespace AtomicEngine
 
         }
 
-        Vector<CSComponent> componentVector = new Vector<CSComponent>();
+        Vector<CSComponent> csComponentVector = new Vector<CSComponent>();
 
         Dictionary<CSComponentInfo, List<CSComponent>> cscomponents = new Dictionary<CSComponentInfo, List<CSComponent>>();
         List<CSComponent> cscomponentStart = new List<CSComponent>();        

+ 4 - 1
Script/Packages/Atomic/Container.json

@@ -1,5 +1,8 @@
 {
 	"name" : "Container",
 	"sources" : ["Source/Atomic/Container"],
-	"classes" : ["RefCounted"]
+	"classes" : ["RefCounted"],
+	"csharp_interface_decl" : {
+		"RefCounted" : ["IDisposable"]
+	}
 }

+ 8 - 1
Source/Atomic/Graphics/RenderSurface.cpp

@@ -36,7 +36,14 @@ namespace Atomic
 
 RenderSurface::~RenderSurface()
 {
-    Release();
+    // ATOMIC BEGIN
+    // only release if parent texture hasn't expired, in that case 
+    // parent texture was deleted and will have called release on render surface
+    if (!parentTexture_.Expired())
+    {
+        Release();
+    }
+    // ATOMIC END
 }
 
 void RenderSurface::SetNumViewports(unsigned num)

+ 8 - 1
Source/Atomic/Graphics/RenderSurface.h

@@ -113,8 +113,15 @@ public:
     unsigned GetRenderBuffer() const { return renderBuffer_; }
 
 private:
+
+// ATOMIC BEGIN
+     
+    /// ATOMIC: changing to WeakPtr to prevent double release when parentTexture is deleted first
     /// Parent texture.
-    Texture* parentTexture_;
+    WeakPtr<Texture> parentTexture_;
+
+    // ATOMIC_END
+
 
     union
     {

+ 6 - 4
Source/Atomic/Math/StringHash.cpp

@@ -80,20 +80,22 @@ String StringHash::ToString() const
 // Lookup for significant strings, not a member of StringHash so don't need to drag hashmap into header
 static HashMap<unsigned, String> gSignificantLookup;
 
-void StringHash::RegisterSignificantString(const char* str)
+StringHash StringHash::RegisterSignificantString(const char* str)
 {
     unsigned hash = Calculate(str);
 
     if (gSignificantLookup.Contains(hash))
-        return;
+        return StringHash(hash);
 
     gSignificantLookup[hash] = String(str);
 
+    return StringHash(hash);
+
 }
 
-void StringHash::RegisterSignificantString(const String& str)
+StringHash StringHash::RegisterSignificantString(const String& str)
 {
-    RegisterSignificantString(str.CString());
+    return RegisterSignificantString(str.CString());
 }
 
 bool StringHash::GetSignificantString(unsigned hash, String& strOut)

+ 2 - 2
Source/Atomic/Math/StringHash.h

@@ -109,10 +109,10 @@ public:
     // ATOMIC BEGIN
 
     /// Register significant C string, which can be looked up via hash, note that the lookup is case insensitive
-    static void RegisterSignificantString(const char* str);
+    static StringHash RegisterSignificantString(const char* str);
 
     /// Register significant string, which can be looked up via hash, note that the lookup is case insensitive
-    static void RegisterSignificantString(const String& str);
+    static StringHash RegisterSignificantString(const String& str);
 
     /// Get a significant string from a case insensitive hash value
     static bool GetSignificantString(unsigned hash, String& strOut);

+ 227 - 23
Source/Atomic/Metrics/Metrics.cpp

@@ -22,6 +22,8 @@
 
 #include "../IO/Log.h"
 
+#include "../Scene/Node.h"
+#include "../Script/ScriptComponent.h"
 #include "../Metrics/Metrics.h"
 
 #ifdef ATOMIC_PLATFORM_WEB
@@ -57,6 +59,30 @@ void MetricsSnapshot::Clear()
     resourceMetrics_.Clear();
 }
 
+void MetricsSnapshot::RegisterInstance(const String& classname, InstantiationType instantiationType, int count)
+{
+    InstanceMetric *metric = &instanceMetrics_[classname];
+
+    if (!metric->classname.Length())
+        metric->classname = classname;
+
+    metric->count += count;
+
+    switch (instantiationType)
+    {
+    case INSTANTIATION_NATIVE:
+        metric->nativeInstances++;
+        break;
+    case INSTANTIATION_JAVASCRIPT:
+        metric->jsInstances++;
+        break;
+    case INSTANTIATION_NET:
+        metric->netInstances++;
+        break;
+    }
+
+}
+
 String MetricsSnapshot::PrintData(unsigned columns, unsigned minCount)
 {
     String output;
@@ -139,29 +165,131 @@ Metrics::~Metrics()
     Metrics::metrics_ = 0;
 }
 
-void Metrics::CaptureInstances(MetricsSnapshot* snapshot)
+void Metrics::ProcessInstances()
 {
-    const String unkClassName("-Unknown Class-");
+    const static StringHash scriptComponentType("ScriptComponent");
+    const static StringHash jsComponentType("JSComponent");
+    const static StringHash csComponentType("CSComponent");
 
-    for (unsigned i = 0; i < instances_.Size(); i++)
+    RefCountedInfo ninfo;
+    PODVector<RefCounted*>::ConstIterator itr = processInstances_.Begin();
+
+    while (itr != processInstances_.End())
     {
-        RefCounted* r = instances_[i];
+        RefCounted* r = *itr;
+
+        Object* o = r->IsObject() ? (Object *)r : 0;
+
+        const String& typeName = r->GetTypeName();
 
-        const String& name = r->GetTypeName();
+        ninfo.refCounted = r;
+        ninfo.instantiationType = r->GetInstantiationType();
 
-        MetricsSnapshot::InstanceMetric& metric = snapshot->instanceMetrics_[name];
+        ninfo.typeID = StringHash(typeName);
+
+        if (!names_.Contains(ninfo.typeID))
+        {
+            names_[ninfo.typeID] = typeName;
+        }
+        
+        ninfo.typeNameOverride = StringHash::ZERO;
+       
+        // for script components, setup typeNameOverride
+        if (o && o->IsInstanceOf(scriptComponentType))
+        {
+            const String& classname = ((ScriptComponent*)o)->GetComponentClassName();
+
+            if (classname.Length())
+            {
+                String name;
+
+                if (o->GetType() == jsComponentType)
+                {
+                    name = classname + " (JS)";
+                }                    
+                else
+                {
+                    name = classname + " (C#)";
+                }
+
+                ninfo.typeNameOverride = StringHash(name);
+
+                if (!names_.Contains(ninfo.typeNameOverride))
+                {
+                    names_[ninfo.typeNameOverride] = name;
+                }
+
+            }
+        }
+        
+        PODVector<RefCountedInfo>& infos = instances_[ninfo.typeID];
 
-        metric.classname = name;
-        metric.count++;
+        RefCountedInfo* info = infos.Buffer();
+        int count = (int) infos.Size();
 
-        if (r->GetInstantiationType() == INSTANTIATION_NATIVE)
-            metric.nativeInstances++;
-        else if (r->GetInstantiationType() == INSTANTIATION_JAVASCRIPT)
-            metric.jsInstances++;
-        else if (r->GetInstantiationType() == INSTANTIATION_NET)
-            metric.netInstances++;
+        while (count > 0)
+        {
+            if (!info->refCounted)
+            {
+                *info = ninfo;
+                break;
+            }
+
+            count--;
+            info++;
+        }
+
+        if (!count)
+        {
+            if (infos.Capacity() <= (infos.Size() + 1))
+            {
+                infos.Reserve(infos.Size() + 32768);
+            }
+
+            infos.Push(ninfo);
+        }
+
+        itr++;
     }
 
+    processInstances_.Clear();
+}
+
+void Metrics::CaptureInstances(MetricsSnapshot* snapshot)
+{    
+    ProcessInstances();
+
+    HashMap<StringHash, PODVector<RefCountedInfo>>::ConstIterator itr = instances_.Begin();
+
+    while (itr != instances_.End())
+    {
+        StringHash typeID = itr->first_;
+        String* typeName = &names_[typeID];
+
+        RefCountedInfo* info = itr->second_.Buffer();
+        int count = (int) itr->second_.Size();
+
+        while (count > 0)
+        {
+            // free spot
+            if (!info->refCounted)
+            {
+                info++;
+                count--;
+                continue;
+            }
+
+            if (info->typeNameOverride != StringHash::ZERO)
+                typeName = &names_[info->typeNameOverride];
+
+            snapshot->RegisterInstance(*typeName, info->instantiationType);
+
+            info++;
+            count--;
+        }
+
+        itr++;
+    }
 }
 
 void Metrics::Capture(MetricsSnapshot* snapshot)
@@ -194,6 +322,9 @@ bool Metrics::Enable()
 
     enabled_ = everEnabled_ = true;
 
+    ATOMIC_LOGINFO("Metrics subsystem enabled, performance will be degraded and there may be stutter while instrumenting");
+    ATOMIC_LOGINFO("IMPORTANT: Do not ship applications with performance metrics enabled");
+
     RefCounted::AddRefCountedCreatedFunction(Metrics::OnRefCountedCreated);
     RefCounted::AddRefCountedDeletedFunction(Metrics::OnRefCountedDeleted);
 
@@ -213,6 +344,86 @@ void Metrics::Disable()
     RefCounted::RemoveRefCountedDeletedFunction(Metrics::OnRefCountedDeleted);
 }
 
+
+String Metrics::PrintNodeNames() const
+{
+    const static StringHash nodeType("Node");
+
+    String output;
+
+    PODVector<RefCountedInfo>& infos = instances_[nodeType];
+
+    PODVector<RefCountedInfo>::Iterator itr = infos.Begin();
+
+    for (; itr != infos.End(); itr++)
+    {
+        RefCounted* r = itr->refCounted;
+
+        if (!r)
+            continue;
+
+        Object* o = r->IsObject() ? (Object *)r : 0;
+
+        if (!o)
+            continue;
+
+        if (o->GetType() == nodeType)
+        {
+            const String& name = ((Node*)o)->GetName();
+            output.AppendWithFormat("Node: %s\n", name.Length() ? name.CString() : "Anonymous Node");
+        }
+
+    }
+
+    return output;
+}
+
+void Metrics::AddRefCounted(RefCounted* refCounted)
+{
+    // We're called from the RefCounted constructor, so we don't know whether we're an object, etc
+
+    if (processInstances_.Capacity() <= (processInstances_.Size() + 1))
+    {
+        processInstances_.Reserve(processInstances_.Size() + 32768);
+    }
+
+    processInstances_.Push(refCounted);
+
+}
+
+void Metrics::RemoveRefCounted(RefCounted* refCounted)
+{
+    processInstances_.Remove(refCounted);
+
+    // this is called from RefCounted destructor, so can't access refCounted though pointer address is still valid
+    // we also don't want to hash every RefCounted ptr due to possible collisions, so need to search:
+
+    HashMap<StringHash, PODVector<RefCountedInfo>>::Iterator itr = instances_.Begin();
+
+    while (itr != instances_.End())
+    {
+        RefCountedInfo* info = itr->second_.Buffer();
+        int count = (int)itr->second_.Size();
+
+        while (count > 0)
+        {
+            if (info->refCounted == refCounted)
+            {
+                info->refCounted = 0;
+                break;
+            }
+
+            info++;
+            count--;
+        }
+
+        if (count)
+            break;
+
+        itr++;
+    }
+}
+
 void Metrics::OnRefCountedCreated(RefCounted* refCounted)
 {
     if (!metrics_)
@@ -221,9 +432,7 @@ void Metrics::OnRefCountedCreated(RefCounted* refCounted)
         return;
     }
 
-    // We're called from the RefCounted constructor, so we don't know whether we're an object, etc
-    metrics_->instances_.Push(refCounted);
-
+    metrics_->AddRefCounted(refCounted);
 }
 
 void Metrics::OnRefCountedDeleted(RefCounted* refCounted)
@@ -234,12 +443,7 @@ void Metrics::OnRefCountedDeleted(RefCounted* refCounted)
         return;
     }
 
-    Vector<RefCounted*>::Iterator itr = metrics_->instances_.Find(refCounted);
-
-    if (itr != metrics_->instances_.End())
-    {
-        metrics_->instances_.Erase(itr);
-    }
+    metrics_->RemoveRefCounted(refCounted);    
 }
 
 

+ 38 - 2
Source/Atomic/Metrics/Metrics.h

@@ -23,6 +23,7 @@
 #pragma once
 
 #include "../Core/Object.h"
+#include "../Container/List.h"
 
 namespace Atomic
 {
@@ -41,6 +42,9 @@ public:
 
     void Clear();
 
+    /// Register instance(s) of classname in metrics snapshot
+    void RegisterInstance(const String& classname, InstantiationType instantiationType, int count = 1);
+
 private:
 
     struct InstanceMetric
@@ -108,28 +112,60 @@ public:
     /// Destruct.
     virtual ~Metrics();
 
+    /// Enable the Metrics subsystem and start capturing instance data, expensive on CPU and may cause stuttering while enabled
     bool Enable();
 
+    /// Get whether the Metrics subsystem is enabled or not
     bool GetEnabled() const { return enabled_; }    
 
+    // Captures a snapshot of metrics data
     void Capture(MetricsSnapshot* snapshot);
 
-private:    
+    /// Prints names of registered node instances output string
+    String PrintNodeNames() const;
+
+private:
+
+    // A RefCountedInfo entry, necessary as we need to access instances in RefCounted constructor/destructor
+    // and info is not available (pure call access violations on abstract virtual functions)
+    struct RefCountedInfo
+    {
+        // pointer to corresponding RefCounted, null means a free entry
+        RefCounted *refCounted;
+        // the has of the typename
+        StringHash typeID;
+        // for some types, like ScriptComponent it is useful to override the name to the JSComponent/CSComponent
+        StringHash typeNameOverride;
+        // the instantiation type of the instance
+        InstantiationType instantiationType;
+    };
 
     void Disable();
 
     void CaptureInstances(MetricsSnapshot* snapshot);
+    void ProcessInstances();
 
     static void OnRefCountedCreated(RefCounted* refCounted);
     static void OnRefCountedDeleted(RefCounted* refCounted);
 
+    void AddRefCounted(RefCounted* refCounted);
+    void RemoveRefCounted(RefCounted* refCounted);
+
+    // static instance
     static Metrics* metrics_;
 
     static bool everEnabled_;
 
     bool enabled_;
 
-    Vector<RefCounted*> instances_;
+    // typeid -> information for individual RefCounted
+    mutable HashMap<StringHash, PODVector<RefCountedInfo>> instances_;
+
+    // instances added since last capture, see note on RefCountedInfo as to why necessary
+    PODVector<RefCounted*> processInstances_;
+
+    // Lookup from string hashes to avoid String thrashing in the metrics subsystem with large numbers of instances
+    HashMap<StringHash, String> names_;
 
 };
 

+ 26 - 0
Source/Atomic/Resource/ResourceCache.cpp

@@ -1244,6 +1244,32 @@ SharedPtr<ResourceNameIterator> ResourceCache::Scan(const String& pathName, cons
 
     return enumerator;
 }
+
+String ResourceCache::PrintResources(const String& typeName) const
+{
+
+    StringHash typeNameHash(typeName);
+
+    String output = "Resource Type         Refs   WeakRefs  Name\n\n";
+
+    for (HashMap<StringHash, ResourceGroup>::ConstIterator cit = resourceGroups_.Begin(); cit != resourceGroups_.End(); ++cit)
+    {
+        for (HashMap<StringHash, SharedPtr<Resource> >::ConstIterator resIt = cit->second_.resources_.Begin(); resIt != cit->second_.resources_.End(); ++resIt)
+        {
+            Resource* resource = resIt->second_;
+
+            // filter
+            if (typeName.Length() && resource->GetType() != typeNameHash)
+                continue;
+
+            output.AppendWithFormat("%s     %i     %i     %s\n",resource->GetTypeName().CString(), resource->Refs(), resource->WeakRefs(), resource->GetName().CString());
+        }
+
+    }
+
+    return output;
+}
+
 // ATOMIC END
 
 }

+ 3 - 0
Source/Atomic/Resource/ResourceCache.h

@@ -245,6 +245,9 @@ public:
     /// Scan specified files, returning them as an iterator
     SharedPtr<ResourceNameIterator> Scan(const String& pathName, const String& filter, unsigned flags, bool recursive) const;
 
+    /// Returns a formatted string containing the currently loaded resources with optional type name filter.
+    String PrintResources(const String& typeName = String::EMPTY) const;
+
     // ATOMIC END
 
 private:

+ 2 - 0
Source/Atomic/Script/ScriptVariant.cpp

@@ -39,6 +39,8 @@ Resource* ScriptVariant::GetResource() const
 
         return cache->GetResource(ref.type_, ref.name_);       
     }
+
+    return 0;
 }
 
 void ScriptVariant::SetResource(Resource* resource)

+ 24 - 21
Source/Atomic/UI/SystemUI/DebugHud.cpp

@@ -233,17 +233,34 @@ void DebugHud::Update(float timeStep)
                 {
                     Metrics* metrics = GetSubsystem<Metrics>();
 
+                    int size = profilerText_->GetFontSize();
+
                     if (metrics)
-                    {
-                        if (!metrics->GetEnabled())
-                            metrics->Enable();
+                    {                        
+
+                        if (metrics->GetEnabled())
+                        {
+                            if (size != 8)
+                                profilerText_->SetFont(profilerText_->GetFont(), 8);
+
+                            SharedPtr<MetricsSnapshot> snapshot(new MetricsSnapshot());
+                            metrics->Capture(snapshot);
+                            profilerOutput = snapshot->PrintData(2);
+                        }
+                        else
+                        {
+                            if (size != 32)
+                                profilerText_->SetFont(profilerText_->GetFont(), 32);
+
+                            profilerOutput = "Metrics system not enabled";
+                        }
 
-                        SharedPtr<MetricsSnapshot> snapshot(new MetricsSnapshot());
-                        metrics->Capture(snapshot);
-                        profilerOutput = snapshot->PrintData(2);
                     }
                     else
                     {
+                        if (size != 32)
+                            profilerText_->SetFont(profilerText_->GetFont(), 32);
+
                         profilerOutput = "Metrics subsystem not found";
                     }
 
@@ -271,23 +288,9 @@ void DebugHud::SetProfilerMode(DebugHudProfileMode mode)
     }
     else
     {
-        int size = 8;
-
-        Metrics* metrics = GetSubsystem<Metrics>();
-
-        if (!metrics)
-            size = 32;
-        else
-        {
-            // Enable metrics immediately
-            if (!metrics->GetEnabled())
-                metrics->Enable();
-        }
-
         if (profilerText_.NotNull())
         {
-            profilerText_->SetText("");
-            profilerText_->SetFont(profilerText_->GetFont(), size);
+            profilerText_->SetText("");            
         }
     }
 

+ 49 - 1
Source/AtomicNET/NETNative/NETCInterop.cpp

@@ -1,4 +1,5 @@
 
+#include <Atomic/Core/Thread.h>
 
 #include <Atomic/Resource/ResourceCache.h>
 #include <Atomic/Script/ScriptVariant.h>
@@ -65,14 +66,57 @@ namespace Atomic
             return refCounted->GetClassID();
         }
 
+        ATOMIC_EXPORT_API void csi_AtomicEngine_AddRef(RefCounted* refCounted)
+        {
+            if (!NETCore::EnsureMainThread("csi_AtomicEngine_AddRef - not on main thread"))
+                return;
+
+            if (!refCounted)
+                return;
+
+            refCounted->AddRef();
+        }
+
+
+        ATOMIC_EXPORT_API void csi_AtomicEngine_AddRefSilent(RefCounted* refCounted)
+        {
+            if (!NETCore::EnsureMainThread("csi_AtomicEngine_AddRefSilent - not on main thread"))
+                return;
+
+            if (!refCounted)
+                return;
+
+            refCounted->AddRefSilent();
+        }
+
+
         ATOMIC_EXPORT_API void csi_AtomicEngine_ReleaseRef(RefCounted* refCounted)
         {
+            if (!NETCore::EnsureMainThread("csi_AtomicEngine_ReleaseRef - not on main thread"))
+                return;
+
             if (!refCounted)
                 return;
 
             refCounted->ReleaseRef();
         }
 
+        ATOMIC_EXPORT_API void csi_AtomicEngine_ReleaseRefSilent(RefCounted* refCounted)
+        {
+            if (!NETCore::EnsureMainThread("csi_AtomicEngine_ReleaseRefSilent - not on main thread"))
+                return;
+
+            if (!refCounted)
+                return;
+
+            refCounted->ReleaseRefSilent();
+        }
+
+        ATOMIC_EXPORT_API bool csi_AtomicEngine_IsMainThread()
+        {
+            return Thread::IsMainThread();
+        }
+
         ATOMIC_EXPORT_API const char* csi_Atomic_RefCounted_GetTypeName(RefCounted* self)
         {
             return self ? self->GetTypeName().CString() : "(NULL)";
@@ -101,7 +145,10 @@ namespace Atomic
         }
 
 
-        ATOMIC_EXPORT_API ClassID csi_Atomic_NETCore_Initialize(NETCoreEventDispatchFunction eventDispatch, NETCoreUpdateDispatchFunction updateDispatch, NETCoreRefCountedDeletedFunction refCountedDeleted)
+        ATOMIC_EXPORT_API ClassID csi_Atomic_NETCore_Initialize(NETCoreEventDispatchFunction eventDispatch, 
+            NETCoreUpdateDispatchFunction updateDispatch, 
+            NETCoreRefCountedDeletedFunction refCountedDeleted,
+            NETCoreThrowManagedExceptionFunction throwManagedException)
         {
             Context* context = new Context();
 
@@ -110,6 +157,7 @@ namespace Atomic
             delegates.eventDispatch = eventDispatch;
             delegates.updateDispatch = updateDispatch;
             delegates.refCountedDeleted = refCountedDeleted;
+            delegates.throwManagedException = throwManagedException;
 
             NETCore* netCore = new NETCore(context, &delegates);
             context->RegisterSubsystem(netCore);

+ 24 - 0
Source/AtomicNET/NETNative/NETCore.cpp

@@ -1,6 +1,7 @@
 
 #include <Atomic/Math/MathDefs.h>
 #include <Atomic/Core/ProcessUtils.h>
+#include <Atomic/Core/Thread.h>
 #include <Atomic/IO/Log.h>
 #include <Atomic/Script/ScriptVariantMap.h>
 
@@ -14,6 +15,7 @@ SharedPtr<Context> NETCore::csContext_;
 NETCoreEventDispatchFunction NETCore::eventDispatch_ = nullptr;
 NETCoreUpdateDispatchFunction NETCore::updateDispatch_ = nullptr;
 NETCoreRefCountedDeletedFunction NETCore::refCountedDeleted_ = nullptr;
+NETCoreThrowManagedExceptionFunction NETCore::throwManagedException_ = nullptr;
 
 NETCore::NETCore(Context* context, NETCoreDelegates* delegates) :
     Object(context)
@@ -24,6 +26,7 @@ NETCore::NETCore(Context* context, NETCoreDelegates* delegates) :
     eventDispatch_ = delegates->eventDispatch;
     updateDispatch_ = delegates->updateDispatch;
     refCountedDeleted_ = delegates->refCountedDeleted;
+    throwManagedException_ = delegates->throwManagedException;
 
     NETEventDispatcher* dispatcher = new NETEventDispatcher(context_);
     context_->RegisterSubsystem(dispatcher);
@@ -38,6 +41,21 @@ NETCore::~NETCore()
     assert (!csContext_);    
 }
 
+bool NETCore::EnsureMainThread(const String& throwMsg)
+{
+    if (!Thread::IsMainThread())
+    {
+        if (throwMsg.Length())
+        {
+            NETCore::ThrowManagedException(throwMsg.CString());
+        }
+
+        return false;
+    }
+
+    return true;
+}
+
 void NETCore::OnRefCountedDeleted(RefCounted* ref)
 {
     if (csContext_.Null())
@@ -63,6 +81,12 @@ void NETCore::Shutdown()
 
     eventDispatch_ = nullptr;
     csContext_ = nullptr;
+
+    eventDispatch_ = nullptr;
+    updateDispatch_ = nullptr;
+    refCountedDeleted_ = nullptr;
+    throwManagedException_ = nullptr;
+
 }
 
 }

+ 8 - 0
Source/AtomicNET/NETNative/NETCore.h

@@ -31,12 +31,14 @@ namespace Atomic
 typedef void (*NETCoreEventDispatchFunction)(Object* refCounted, unsigned eventID, VariantMap* eventData);
 typedef void (*NETCoreUpdateDispatchFunction)(float timeStep);
 typedef void (*NETCoreRefCountedDeletedFunction)(RefCounted* refCounted);
+typedef void(*NETCoreThrowManagedExceptionFunction)(const char* errorMsg);
 
 struct NETCoreDelegates
 {
     NETCoreEventDispatchFunction eventDispatch;
     NETCoreUpdateDispatchFunction updateDispatch;
     NETCoreRefCountedDeletedFunction refCountedDeleted;
+    NETCoreThrowManagedExceptionFunction throwManagedException;
 };
 
 class ATOMIC_API NETCore : public Object
@@ -54,11 +56,16 @@ public:
 
     static void Shutdown();
 
+    static bool EnsureMainThread(const String& throwMsg);
+
     static void RegisterNETEventType(unsigned eventType);
 
     inline static void DispatchEvent(Object* refCounted, unsigned eventID, VariantMap* eventData = nullptr) { eventDispatch_(refCounted, eventID, eventData); }
     inline static void DispatchUpdateEvent(float timeStep) { if (updateDispatch_) updateDispatch_(timeStep); }
 
+    /// Throws a managed exception in managed code from native code
+    inline static void ThrowManagedException(const String& errorMsg) { if (throwManagedException_) throwManagedException_(errorMsg.CString()); }
+
     /// We access this directly in binding code, where there isn't a context
     /// to get a reference from
     static inline Context* GetContext() { return csContext_; }
@@ -72,6 +79,7 @@ private:
     static NETCoreUpdateDispatchFunction updateDispatch_;
     static NETCoreEventDispatchFunction eventDispatch_;
     static NETCoreRefCountedDeletedFunction refCountedDeleted_;
+    static NETCoreThrowManagedExceptionFunction throwManagedException_;
 
 };
 

+ 4 - 0
Source/ToolCore/Command/BindCmd.cpp

@@ -24,6 +24,7 @@
 #include <Atomic/Core/StringUtils.h>
 #include <Atomic/IO/Log.h>
 #include <Atomic/IO/File.h>
+#include <Atomic/IO/FileSystem.h>
 
 #include "../ToolSystem.h"
 #include "../ToolEnvironment.h"
@@ -70,6 +71,9 @@ bool BindCmd::ParseInternal(const Vector<String>& arguments, unsigned startIndex
         return false;
     }
 
+    sourceRootFolder_ = AddTrailingSlash(sourceRootFolder_);
+    packageFolder_ = AddTrailingSlash(packageFolder_);
+
     return true;
 }
 

+ 20 - 12
Source/ToolCore/JSBind/CSharp/CSClassWriter.cpp

@@ -221,25 +221,33 @@ void CSClassWriter::GenerateManagedSource(String& sourceOut)
         source += IndentLine("/// </summary>\n");
     }
 
-    if (klass_->GetBaseClass())
-    {
+    JSBClass* baseClass = klass_->GetBaseClass();
+    const StringVector& csharpInterfaces = klass_->GetCSharpInterfaces();
 
-        String baseString = klass_->GetBaseClass()->GetName();
+    if (baseClass || csharpInterfaces.Size())
+    {
+        StringVector baseStrings;
 
-        const PODVector<JSBClass*>& interfaces = klass_->GetInterfaces();
+        if (baseClass)
+        {
+            baseStrings.Push(baseClass->GetName());
+        }
+        
+        const PODVector<JSBClass*>& nativeInterfaces = klass_->GetInterfaces();
 
-        if (interfaces.Size())
+        for (unsigned i = 0; i < nativeInterfaces.Size(); i++)
         {
-            StringVector baseStrings;
-            baseStrings.Push(baseString);
-            for (unsigned i = 0; i < interfaces.Size(); i++)
-            {
-                baseStrings.Push(interfaces.At(i)->GetName());
-            }
+            baseStrings.Push(nativeInterfaces.At(i)->GetName());
+        }
 
-            baseString = String::Joined(baseStrings, ",");
+        for (unsigned i = 0; i < csharpInterfaces.Size(); i++)
+        {
+            baseStrings.Push(csharpInterfaces[i]);
         }
 
+
+        String baseString = String::Joined(baseStrings, ",");
+
         line = ToString("public partial class %s%s : %s\n", klass_->GetName().CString(), klass_->IsGeneric() ? "<T>" : "", baseString.CString());
     }
     else

+ 14 - 3
Source/ToolCore/JSBind/CSharp/CSFunctionWriter.cpp

@@ -816,7 +816,18 @@ void CSFunctionWriter::WriteManagedFunction(String& source)
         }
         else if (function_->GetReturnType()->type_->asVectorType())
         {
-            source += IndentLine(ToString("var returnScriptVector = %s%s%uReturnValue.GetScriptVector();\n", klass->GetName().CString(), function_->GetName().CString(), function_->GetID()));
+            JSBVectorType* vtype = function_->GetReturnType()->type_->asVectorType();
+            
+            String marshalName = ToString("%s%s%uReturnValue", function_->GetClass()->GetName().CString(), function_->GetName().CString(), function_->GetID());
+            
+            // Defer creation of ScriptVector return value until method is called
+            if (vtype->vectorType_->asClassType())
+            {
+                String classname = vtype->vectorType_->asClassType()->class_->GetName();
+                source += IndentLine(ToString("if (%s == null) %s = new Vector<%s>();\n", marshalName.CString(), marshalName.CString(), classname.CString()));
+            }
+
+            source += IndentLine(ToString("var returnScriptVector = %s.GetScriptVector();\n", marshalName.CString()));
         }
         else if (CSTypeHelper::IsSimpleReturn(function_->GetReturnType()))
             line += "return ";
@@ -944,7 +955,7 @@ void CSFunctionWriter::GenerateManagedSource(String& sourceOut)
 
                     marshal += managedType + " ";
 
-                    marshal += ToString("%s%s%uReturnValue = new %s();\n", klass->GetName().CString(), function_->GetName().CString(), function_->GetID(), managedType.CString());
+                    marshal += ToString("%s%s%uReturnValue;\n", klass->GetName().CString(), function_->GetName().CString(), function_->GetID());
 
                     sourceOut += IndentLine(marshal);
                 }
@@ -961,7 +972,7 @@ void CSFunctionWriter::GenerateManagedSource(String& sourceOut)
 
                 String marshal = "private " + typestring + " ";
 
-                marshal += ToString("%s%s%uReturnValue = new %s();\n", function_->GetClass()->GetName().CString(), function_->GetName().CString(), function_->GetID(), typestring.CString());
+                marshal += ToString("%s%s%uReturnValue = null;\n", function_->GetClass()->GetName().CString(), function_->GetName().CString(), function_->GetID());
 
                 sourceOut += IndentLine(marshal);
 

+ 7 - 0
Source/ToolCore/JSBind/JSBClass.h

@@ -191,6 +191,12 @@ public:
     unsigned GetNumHaxeDecl() { return haxeDecls_.Size(); }
     const String& GetHaxeDecl(unsigned idx) { return haxeDecls_[idx]; }
 
+    /// CSharp bindings can add NET interfaces to native classes, for example IDisposable, IEquatable, etc
+    void AddCSharpInterface(const String& interface) { csharpInterfacesDecls_.Push(interface); }
+
+    /// Gets the C# interfaces implemented by this class
+    const StringVector& GetCSharpInterfaces() const { return csharpInterfacesDecls_; }
+
     void Preprocess();
     void Process();
     void PostProcess();
@@ -217,6 +223,7 @@ private:
 
     Vector<String> typeScriptDecls_;
     Vector<String> haxeDecls_;
+    Vector<String> csharpInterfacesDecls_;
 
     bool isAbstract_;
     bool isObject_;

+ 35 - 0
Source/ToolCore/JSBind/JSBModule.cpp

@@ -108,6 +108,7 @@ void JSBModule::VisitHeaders()
     ProcessClassExcludes();
     ProcessTypeScriptDecl();
     ProcessHaxeDecl();
+    ProcessCSharpDecl();
 }
 
 void JSBModule::PreprocessClasses()
@@ -364,6 +365,40 @@ void JSBModule::ProcessHaxeDecl()
     }
 }
 
+void JSBModule::ProcessCSharpDecl()
+{
+    // C# declarations
+
+    JSONValue& root = moduleJSON_->GetRoot();
+
+    JSONValue interfaceDecl = root.Get("csharp_interface_decl");
+
+    if (interfaceDecl.IsObject())
+    {
+        Vector<String> childNames = interfaceDecl.GetObject().Keys();
+
+        for (unsigned j = 0; j < childNames.Size(); j++)
+        {
+            String classname = childNames.At(j);
+
+            JSBClass* klass = GetClass(classname);
+
+            if (!klass)
+            {
+                ErrorExit("Bad csharp decl class");
+            }
+
+            JSONArray interfaces = interfaceDecl.Get(classname).GetArray();
+
+            for (unsigned k = 0; k < interfaces.Size(); k++)
+            {
+                klass->AddCSharpInterface(interfaces[k].GetString());
+            }
+        }
+    }
+
+}
+
 void JSBModule::ScanHeaders()
 {
     JSBind* jsbind = GetSubsystem<JSBind>();

+ 3 - 0
Source/ToolCore/JSBind/JSBModule.h

@@ -114,6 +114,9 @@ private:
     void ProcessTypeScriptDecl();
     void ProcessHaxeDecl();
 
+    /// Process CSharp declarations, including interfaces
+    void ProcessCSharpDecl();
+
     void ScanHeaders();
 
     String name_;

+ 1 - 1
Submodules/AtomicExamples

@@ -1 +1 @@
-Subproject commit c449e69458e5e07fea0385bebb0cb77ee3197cd9
+Subproject commit 6b55cdb8cf1e20b1c8ac9fd43ea4f8b33b8710d1