Browse Source

Engine metrics subsystem

Josh Engebretson 9 năm trước cách đây
mục cha
commit
40200b05ed
37 tập tin đã thay đổi với 874 bổ sung109 xóa
  1. 13 2
      Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts
  2. 1 0
      Script/AtomicNET/AtomicNET/Core/AtomicNET.cs
  3. 7 2
      Script/AtomicNET/AtomicNET/Core/NativeCore.cs
  4. 6 0
      Script/AtomicNET/AtomicNET/Player/Player.cs
  5. 86 62
      Script/AtomicNET/AtomicNET/Scene/Scene.cs
  6. 9 9
      Script/AtomicNET/AtomicNET/Script/ScriptVariant.cs
  7. 3 0
      Script/Packages/Atomic/Core.json
  8. 5 0
      Script/Packages/Atomic/Metrics.json
  9. 1 1
      Script/Packages/Atomic/Package.json
  10. 3 1
      Source/Atomic/CMakeLists.txt
  11. 23 1
      Source/Atomic/Container/RefCounted.cpp
  12. 19 1
      Source/Atomic/Container/RefCounted.h
  13. 5 1
      Source/Atomic/Core/Context.cpp
  14. 7 1
      Source/Atomic/Core/Object.h
  15. 20 0
      Source/Atomic/Engine/Application.cpp
  16. 12 0
      Source/Atomic/Engine/Application.h
  17. 4 0
      Source/Atomic/Engine/Engine.cpp
  18. 265 0
      Source/Atomic/Metrics/Metrics.cpp
  19. 139 0
      Source/Atomic/Metrics/Metrics.h
  20. 64 1
      Source/Atomic/UI/SystemUI/DebugHud.cpp
  21. 9 0
      Source/Atomic/UI/SystemUI/DebugHud.h
  22. 1 1
      Source/Atomic/UI/SystemUI/Text.cpp
  23. 10 0
      Source/Atomic/UI/UI.cpp
  24. 6 0
      Source/Atomic/UI/UI.h
  25. 33 0
      Source/Atomic/UI/UIEnums.h
  26. 5 0
      Source/AtomicApp/AppBase.cpp
  27. 2 1
      Source/AtomicApp/AppBase.h
  28. 3 0
      Source/AtomicApp/Player/IPCPlayerApp.cpp
  29. 13 0
      Source/AtomicEditor/EditorMode/AEEditorMode.cpp
  30. 0 3
      Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp
  31. 3 0
      Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp
  32. 4 0
      Source/AtomicJS/Javascript/JSAtomic.cpp
  33. 1 8
      Source/AtomicNET/NETNative/NETCInterop.cpp
  34. 1 1
      Source/AtomicNET/NETScript/CSComponentAssembly.cpp
  35. 69 12
      Source/AtomicPlayer/Player.cpp
  36. 17 1
      Source/AtomicPlayer/Player.h
  37. 5 0
      Source/AtomicPlayer/PlayerEvents.h

+ 13 - 2
Script/AtomicEditor/ui/frames/menus/MainFrameMenu.ts

@@ -271,6 +271,14 @@ class MainFrameMenu extends Atomic.ScriptObject {
             if (refid == "tools toggle profiler") {
                 Atomic.ui.toggleDebugHud();
                 return true;
+            } if (refid == "tools perf profiler") {                
+                Atomic.ui.debugHudProfileMode = Atomic.DebugHudProfileMode.DEBUG_HUD_PROFILE_PERFORMANCE;
+                Atomic.ui.showDebugHud(true);
+                return true;
+            } else if (refid == "tools metrics profiler") {                
+                Atomic.ui.debugHudProfileMode = Atomic.DebugHudProfileMode.DEBUG_HUD_PROFILE_METRICS;
+                Atomic.ui.showDebugHud(true);
+                return true;
             } else if (refid.indexOf("tools log") != -1) {
 
                 let logName = refid.indexOf("editor") != -1 ? "AtomicEditor" : "AtomicPlayer";
@@ -364,8 +372,11 @@ var editItems = {
 };
 
 var toolsItems = {
-
-    "Toggle Profiler": ["tools toggle profiler"],
+    "Profiler": {
+        "Toggle HUD": ["tools toggle profiler"],
+        "Profile Performance": ["tools perf profiler"],
+        "Profile Metrics": ["tools metrics profiler"]
+    },
     "Logs": {
         "Player Log": ["tools log player"],
         "Editor Log": ["tools log editor"]

+ 1 - 0
Script/AtomicNET/AtomicNET/Core/AtomicNET.cs

@@ -92,6 +92,7 @@ namespace AtomicEngine
 
             AtomicAppModule.Initialize();
             ScriptModule.Initialize();
+            MetricsModule.Initialize();
 
             AtomicNETScriptModule.Initialize();
             AtomicNETNativeModule.Initialize();

+ 7 - 2
Script/AtomicNET/AtomicNET/Core/NativeCore.cs

@@ -354,7 +354,7 @@ namespace AtomicEngine
             {
                 if (logWrapUnknownNative)
                 {
-                    Log.Info("WrapNative returning null for unknown class: " + csi_Atomic_RefCounted_GetTypeName(native));
+                    Log.Info("WrapNative returning null for unknown class: " + GetNativeTypeName(native));
                 }
 
                 return null;
@@ -387,8 +387,13 @@ namespace AtomicEngine
             return (T)r;
         }
 
+        internal static string GetNativeTypeName(IntPtr native)
+        {
+            return System.Runtime.InteropServices.Marshal.PtrToStringAnsi(csi_Atomic_RefCounted_GetTypeName(native));
+        }
+
         [DllImport(Constants.LIBNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern string csi_Atomic_RefCounted_GetTypeName(IntPtr self);
+        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);

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

@@ -24,6 +24,12 @@ namespace AtomicPlayer
                     loadedScenes.Remove(e.Scene);
             });
 
+            SubscribeToEvent<PlayerSceneUnloadEvent>(e =>
+            {
+                if (loadedScenes.Contains(e.Scene))
+                    loadedScenes.Remove(e.Scene);
+            });
+
         }
 
 

+ 86 - 62
Script/AtomicNET/AtomicNET/Scene/Scene.cs

@@ -14,18 +14,66 @@ namespace AtomicEngine
             {
                 //Log.Info($"Node ADDED: {e.Node.Name}");
 
+                // TODO: How to handle adding components on existing node?
+
             });
 
             SubscribeToEvent<NodeRemovedEvent>(this, e =>
             {
                 //Log.Info($"Node REMOVED: {e.Node.Name}");
 
+                // 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);
+
+                for (uint i = 0; i < componentVector.Size; i++)
+                {
+                    HandleComponentRemoved(componentVector[i]);
+                }
+
+                componentVector.Clear();
+
             });
 
             SubscribeToEvent<CSComponentLoadEvent>(this, HandleCSComponentLoad);
 
-            SubscribeToEvent<ComponentAddedEvent>(this, HandleComponentAdded);
-            SubscribeToEvent<ComponentRemovedEvent>(this, HandleComponentRemoved);
+            SubscribeToEvent<ComponentAddedEvent>(this, e =>
+            {
+                Component component = null;
+
+                try
+                {
+                    // will throw if component isn't a known native
+                    component = e.Component;
+                }
+                catch
+                {
+                    return;
+                }
+
+                HandleComponentAdded(component);
+
+            });
+
+
+            SubscribeToEvent<ComponentRemovedEvent>(this, e =>
+            {
+                Component component = null;
+
+                try
+                {
+                    // will throw if component isn't a known native
+                    component = e.Component;
+                }
+                catch
+                {
+                    return;
+                }
+
+                HandleComponentRemoved(component);
+
+            });
 
             // Update variable timestep logic
             SubscribeToEvent<SceneUpdateEvent>(this, HandleSceneUpdate);
@@ -186,51 +234,6 @@ namespace AtomicEngine
             }
         }
 
-        void HandleComponentRemoved(ComponentRemovedEvent e)
-        {
-            Component component;
-
-            try
-            {
-                // will throw if component isn't a known native
-                component = e.Component;
-            }
-            catch
-            {
-                return;
-            }
-
-            if (component.GetType() == typeof(PhysicsWorld) || component.GetType() == typeof(PhysicsWorld2D))
-            {
-                UnsubscribeFromEvent<PhysicsPreStepEvent>();
-                UnsubscribeFromEvent<PhysicsPostStepEvent>();
-            }
-
-            if (component.GetType().GetTypeInfo().IsSubclassOf(typeof(CSComponent)))
-            {
-                var csc = (CSComponent)component;
-
-                CSComponentInfo info;
-                if (!CSComponentCore.csinfoLookup.TryGetValue(csc.GetType(), out info))
-                {
-                    return;
-                }
-
-                cscomponentStart.Remove(csc);
-
-                List<CSComponent> cslist;
-
-                if (!cscomponents.TryGetValue(info, out cslist))
-                {
-                    return;
-                }
-
-                cslist.Remove(csc);
-
-            }
-
-        }
-
         void HandleCSComponentLoad(CSComponentLoadEvent e)
         {
             var scriptMap = e.scriptMap;
@@ -303,20 +306,8 @@ namespace AtomicEngine
 
         }
 
-        void HandleComponentAdded(ComponentAddedEvent e)
+        void HandleComponentAdded(Component component)
         {
-            Component component;
-
-            try
-            {
-                // will throw if component isn't a known native
-                component = e.Component;
-            }
-            catch
-            {
-                return;
-            }
-            
 
             // Check null (CSComponent) or other abstract component
             if (component == null)
@@ -324,8 +315,6 @@ namespace AtomicEngine
                 return;
             }
 
-            // Log.Info($"Component {component.TypeName} ADDED From Node {e.Node.Name}");
-
             if (component.GetType() == typeof(PhysicsWorld) || component.GetType() == typeof(PhysicsWorld2D))
             {
                 SubscribeToEvent<PhysicsPreStepEvent>(component, HandlePhysicsPreStep);
@@ -337,11 +326,46 @@ namespace AtomicEngine
             {
                 var csc = (CSComponent)component;
                 AddCSComponent(csc);
+            }
+
+        }
+
+        void HandleComponentRemoved(Component component)
+        {
+            if (component.GetType() == typeof(PhysicsWorld) || component.GetType() == typeof(PhysicsWorld2D))
+            {
+                UnsubscribeFromEvent<PhysicsPreStepEvent>();
+                UnsubscribeFromEvent<PhysicsPostStepEvent>();
+            }
+
+            if (component.GetType().GetTypeInfo().IsSubclassOf(typeof(CSComponent)))
+            {
+                var csc = (CSComponent)component;
+
+                CSComponentInfo info;
+
+                if (!CSComponentCore.csinfoLookup.TryGetValue(csc.GetType(), out info))
+                {
+                    return;
+                }
+
+                cscomponentStart.Remove(csc);
+
+                List<CSComponent> cslist;
+
+                if (!cscomponents.TryGetValue(info, out cslist))
+                {
+                    return;
+                }
+
+                cslist.Remove(csc);
 
             }
 
         }
 
+        Vector<CSComponent> componentVector = new Vector<CSComponent>();
+
         Dictionary<CSComponentInfo, List<CSComponent>> cscomponents = new Dictionary<CSComponentInfo, List<CSComponent>>();
         List<CSComponent> cscomponentStart = new List<CSComponent>();        
 

+ 9 - 9
Script/AtomicNET/AtomicNET/Script/ScriptVariant.cs

@@ -17,17 +17,17 @@ namespace AtomicEngine
             SetVector3(value);
         }
 
-		public ScriptVariant(Vector4 value) : this()
-		{
-			SetVector4(value);
-		}
+        public ScriptVariant(Vector4 value) : this()
+        {
+            SetVector4(value);
+        }
 
-		public ScriptVariant(String value) : this()
-		{
-			SetString(value);
-		}
+        public ScriptVariant(String value) : this()
+        {
+            SetString(value);
+        }
 
-		public ScriptVariant(Color value) : this()
+        public ScriptVariant(Color value) : this()
         {
             SetColor(value);
         }

+ 3 - 0
Script/Packages/Atomic/Core.json

@@ -9,6 +9,9 @@
 		"Object" : {
 			"SendEvent" : ["StringHash"]
 		},
+		"Context" : {
+			"GetTypeName" : ["StringHash"]
+		},
 		"CSharp" : {
 			"Object" : {
 				"UnsubscribeFromAllEvents" : []

+ 5 - 0
Script/Packages/Atomic/Metrics.json

@@ -0,0 +1,5 @@
+{
+	"name" : "Metrics",
+	"sources" : ["Source/Atomic/Metrics"],
+	"classes" : ["Metrics", "MetricsSnapshot"]
+}

+ 1 - 1
Script/Packages/Atomic/Package.json

@@ -4,7 +4,7 @@
 	"namespace" : "Atomic",
 	"modules" : ["Container", "Math", "Core", "Scene", "Graphics", "Atomic2D", "Audio",
 		"Physics", "Navigation", "Input", "UI", "Resource", "Network", "IO",
-		"Engine", "Script", "Javascript", "Environment", "Web", "IPC"],
+		"Engine", "Script", "Javascript", "Environment", "Web", "IPC", "Metrics"],
 		"moduleExclude" : {
 			"WEB" : ["Network", "Navigation", "IPC"],
 			"ANDROID" : ["IPC"],

+ 3 - 1
Source/Atomic/CMakeLists.txt

@@ -13,6 +13,7 @@ file (GLOB AUDIO_SOURCE Audio/*.cpp Audio/*.h)
 file (GLOB NETWORK_SOURCE Network/*.cpp Network/*.h)
 file (GLOB WEB_SOURCE Web/*.cpp Web/*.h)
 file (GLOB SCRIPT_SOURCE Script/*.cpp Script/*.h)
+file (GLOB METRICS_SOURCE Metrics/*.cpp Metrics/*.h)
 
 if (NOT EMSCRIPTEN AND NOT IOS AND NOT ANDROID)
   file (GLOB IPC_SOURCE IPC/*.cpp IPC/*.h)
@@ -60,7 +61,7 @@ set (SOURCE_FILES ${CONTAINER_SOURCE} ${CORE_SOURCE} ${ENGINE_SOURCE} ${INPUT_SO
                   ${ATOMIC3D_SOURCE}
                   ${ATOMIC2D_SOURCE} ${ENVIRONMENT_SOURCE}
                   ${SCENE_SOURCE} ${UI_SOURCE} ${SYSTEM_UI_SOURCE}
-                  ${WEB_SOURCE} ${SCRIPT_SOURCE}
+                  ${WEB_SOURCE} ${SCRIPT_SOURCE} ${METRICS_SOURCE}
                   ${PLATFORM_SOURCE})
 
 if (NOT EMSCRIPTEN)
@@ -87,6 +88,7 @@ GroupSources("Scene")
 GroupSources("UI")
 GroupSources("Web")
 GroupSources("Script")
+GroupSources("Metrics")
 GroupSources("BuildInfo")
 
 # Handle Git Revision

+ 23 - 1
Source/Atomic/Container/RefCounted.cpp

@@ -38,6 +38,11 @@ RefCounted::RefCounted() :
 {
     // Hold a weak ref to self to avoid possible double delete of the refcount
     (refCount_->weakRefs_)++;
+
+// ATOMIC BEGIN
+    for (unsigned i = 0; i < refCountedCreatedFunctions_.Size(); i++)
+        refCountedCreatedFunctions_[i](this);
+// ATOMIC END
 }
 
 RefCounted::~RefCounted()
@@ -54,8 +59,10 @@ RefCounted::~RefCounted()
 
     refCount_ = 0;
 
+// ATOMIC BEGIN
     for (unsigned i = 0; i < refCountedDeletedFunctions_.Size(); i++)
         refCountedDeletedFunctions_[i](this);
+// ATOMIC END
 }
 
 void RefCounted::AddRef()
@@ -63,6 +70,7 @@ void RefCounted::AddRef()
     assert(refCount_->refs_ >= 0);
     (refCount_->refs_)++;
 
+// ATOMIC BEGIN
     if (jsHeapPtr_ && refCount_->refs_ == 2)
     {
         for (unsigned i = 0; i < refCountChangedFunctions_.Size(); i++)
@@ -70,6 +78,7 @@ void RefCounted::AddRef()
             refCountChangedFunctions_[i](this, 2);
         }
     }
+// ATOMIC END
 }
 
 void RefCounted::ReleaseRef()
@@ -77,7 +86,7 @@ void RefCounted::ReleaseRef()
     assert(refCount_->refs_ > 0);
     (refCount_->refs_)--;
 
-
+// ATOMIC BEGIN
     if (jsHeapPtr_ && refCount_->refs_ == 1)
     {
         for (unsigned i = 0; i < refCountChangedFunctions_.Size(); i++)
@@ -93,6 +102,7 @@ void RefCounted::ReleaseRef()
             (refCount_->refs_)--;
         }
     }
+// ATOMIC END
 
     if (!refCount_->refs_)
         delete this;
@@ -112,6 +122,7 @@ int RefCounted::WeakRefs() const
 // ATOMIC BEGIN
 
 PODVector<RefCountChangedFunction> RefCounted::refCountChangedFunctions_;
+PODVector<RefCountedCreatedFunction> RefCounted::refCountedCreatedFunctions_;
 PODVector<RefCountedDeletedFunction> RefCounted::refCountedDeletedFunctions_;
 
 void RefCounted::AddRefSilent()
@@ -120,6 +131,7 @@ void RefCounted::AddRefSilent()
     (refCount_->refs_)++;
 }
 
+
 void RefCounted::AddRefCountChangedFunction(RefCountChangedFunction function)
 {
     refCountChangedFunctions_.Push(function);
@@ -130,6 +142,16 @@ void RefCounted::RemoveRefCountChangedFunction(RefCountChangedFunction function)
     refCountChangedFunctions_.Remove(function);
 }
 
+void RefCounted::AddRefCountedCreatedFunction(RefCountedCreatedFunction function)
+{
+    refCountedCreatedFunctions_.Push(function);
+}
+
+void RefCounted::RemoveRefCountedCreatedFunction(RefCountedCreatedFunction function)
+{
+    refCountedCreatedFunctions_.Remove(function);
+}
+
 void RefCounted::AddRefCountedDeletedFunction(RefCountedDeletedFunction function)
 {
     refCountedDeletedFunctions_.Push(function);

+ 19 - 1
Source/Atomic/Container/RefCounted.h

@@ -25,6 +25,13 @@
 #include "Atomic/Atomic.h"
 #include "Vector.h"
 
+// ATOMIC BEGIN
+
+#include "../Container/Str.h"
+#include "../Math/StringHash.h"
+
+// ATOMIC END
+
 namespace Atomic
 {
 
@@ -43,6 +50,9 @@ class RefCounted;
 // function that is called when ref count goes to 1 or 2+, used for script object lifetime
 typedef void (*RefCountChangedFunction)(RefCounted*, int refCount);
 
+// function callback for when a RefCounted is created
+typedef void(*RefCountedCreatedFunction)(RefCounted*);
+
 // function callback for when a RefCounted is deleted
 typedef void(*RefCountedDeletedFunction)(RefCounted*);
 
@@ -52,7 +62,9 @@ typedef const void* ClassID;
 #define ATOMIC_REFCOUNTED(typeName) \
     public: \
         virtual Atomic::ClassID GetClassID() const { return GetClassIDStatic(); } \
-        static Atomic::ClassID GetClassIDStatic() { static const int typeID = 0; return (Atomic::ClassID) &typeID; }
+        static Atomic::ClassID GetClassIDStatic() { static const int typeID = 0; return (Atomic::ClassID) &typeID; } \
+        virtual const String& GetTypeName() const { return GetTypeNameStatic(); } \
+        static const String& GetTypeNameStatic() { static const String _typeName(#typeName); return _typeName; }
 
 // ATOMIC END
 
@@ -105,6 +117,8 @@ public:
 
     virtual bool IsObject() const { return false; }
 
+    virtual const String& GetTypeName() const = 0;
+
     /// Increment reference count. Do not call any lifetime book keeping
     void AddRefSilent();
 
@@ -121,6 +135,9 @@ public:
     static void AddRefCountChangedFunction(RefCountChangedFunction function);
     static void RemoveRefCountChangedFunction(RefCountChangedFunction function);
 
+    static void AddRefCountedCreatedFunction(RefCountedCreatedFunction function);
+    static void RemoveRefCountedCreatedFunction(RefCountedCreatedFunction function);
+
     static void AddRefCountedDeletedFunction(RefCountedDeletedFunction function);
     static void RemoveRefCountedDeletedFunction(RefCountedDeletedFunction function);
 
@@ -141,6 +158,7 @@ private:
     void* jsHeapPtr_;
 
     static PODVector<RefCountChangedFunction> refCountChangedFunctions_;
+    static PODVector<RefCountedCreatedFunction> refCountedCreatedFunctions_;
     static PODVector<RefCountedDeletedFunction> refCountedDeletedFunctions_;
 
     // ATOMIC END

+ 5 - 1
Source/Atomic/Core/Context.cpp

@@ -224,9 +224,13 @@ Object* Context::GetEventSender() const
 
 const String& Context::GetTypeName(StringHash objectType) const
 {
+    // ATOMIC BEGIN
+
     // Search factories to find the hash-to-name mapping
     HashMap<StringHash, SharedPtr<ObjectFactory> >::ConstIterator i = factories_.Find(objectType);
-    return i != factories_.End() ? i->second_->GetTypeName() : String::EMPTY;
+    return i != factories_.End() ? i->second_->GetFactoryTypeName() : String::EMPTY;
+    
+    // ATOMIC END
 }
 
 AttributeInfo* Context::GetAttribute(StringHash objectType, const char* name)

+ 7 - 1
Source/Atomic/Core/Object.h

@@ -242,8 +242,14 @@ public:
     /// Return type hash of objects created by this factory.
     StringHash GetType() const { return typeInfo_->GetType(); }
 
+    // ATOMIC BEGIN
+
+    // RefCounted in Atomic carry typename with GetTypeName, so renamed to GetFactoryType
+
     /// Return type name of objects created by this factory.
-    const String& GetTypeName() const { return typeInfo_->GetTypeName(); }
+    const String& GetFactoryTypeName() const { return typeInfo_->GetTypeName(); }
+
+    // ATOMIC END
 
 protected:
     /// Execution context.

+ 20 - 0
Source/Atomic/Engine/Application.cpp

@@ -35,9 +35,17 @@
 
 #include "../DebugNew.h"
 
+// ATOMIC BEGIN
+#include "../Metrics/Metrics.h"
+// ATOMIC END
+
 namespace Atomic
 {
 
+// ATOMIC BEGIN
+bool Application::autoMetrics_ = false;
+// ATOMIC END
+
 #if defined(IOS) || defined(__EMSCRIPTEN__)
 // Code for supporting SDL_iPhoneSetAnimationCallback() and emscripten_set_main_loop_arg()
 #if defined(__EMSCRIPTEN__)
@@ -55,6 +63,18 @@ Application::Application(Context* context) :
 {
     engineParameters_ = Engine::ParseParameters(GetArguments());
 
+    // ATOMIC BEGIN
+
+    // register metrics subsystem
+    context->RegisterSubsystem(new Metrics(context));
+
+    if (autoMetrics_ || engineParameters_["AutoMetrics"].GetBool())
+    {
+        context->GetSubsystem<Metrics>()->Enable();
+    }
+
+    // ATOMIC END
+
     // Create the Engine, but do not initialize it yet. Subsystems except Graphics & Renderer are registered at this point
     engine_ = new Engine(context);
 

+ 12 - 0
Source/Atomic/Engine/Application.h

@@ -54,6 +54,12 @@ public:
     /// Show an error message (last log message if empty), terminate the main loop, and set failure exit code.
     void ErrorExit(const String& message = String::EMPTY);
 
+    // ATOMIC BEGIN
+
+    static void SetAutoMetrics(bool value) { autoMetrics_ = value;  }
+
+    // ATOMIC END
+
 protected:
     /// Handle log message.
     void HandleLogMessage(StringHash eventType, VariantMap& eventData);
@@ -66,6 +72,12 @@ protected:
     String startupErrors_;
     /// Application exit code.
     int exitCode_;
+
+    // ATOMIC BEGIN
+
+    static bool autoMetrics_;
+
+    // ATOMIC END
 };
 
 // Macro for defining a main function which creates a Context and the application, then runs it

+ 4 - 0
Source/Atomic/Engine/Engine.cpp

@@ -977,6 +977,10 @@ VariantMap Engine::ParseParameters(const Vector<String>& arguments)
                 ret["LogName"] = value;
                 ++i;
             }
+            else if (argument == "-autometrics") // --autometrics
+            {
+                ret["AutoMetrics"] = true;
+            }
             // ATOMIC END
 #ifdef ATOMIC_TESTING
             else if (argument == "timeout" && !value.Empty())

+ 265 - 0
Source/Atomic/Metrics/Metrics.cpp

@@ -0,0 +1,265 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../IO/Log.h"
+
+#include "../Metrics/Metrics.h"
+
+
+namespace Atomic
+{
+
+Metrics* Metrics::metrics_ = 0;
+bool Metrics::everEnabled_ = false;
+
+bool MetricsSnapshot::CompareInstanceMetrics(const MetricsSnapshot::InstanceMetric& lhs, const MetricsSnapshot::InstanceMetric& rhs)
+{
+    // TODO: Introduce various sorting modes, for now alphabetical is best "only" option
+
+    return lhs.classname < rhs.classname;
+
+    /*
+    if (lhs.count == rhs.count)
+        return lhs.classname < rhs.classname;
+
+    return lhs.count > rhs.count;
+    */
+}
+
+void MetricsSnapshot::Clear()
+{
+    instanceMetrics_.Clear();
+    nodeMetrics_.Clear();
+    resourceMetrics_.Clear();
+}
+
+String MetricsSnapshot::PrintData(unsigned columns, unsigned minCount)
+{
+    String output;
+
+    Vector<MetricsSnapshot::InstanceMetric> instanceSort;
+
+    HashMap<StringHash, InstanceMetric>::ConstIterator instanceItr = instanceMetrics_.Begin();
+
+    while (instanceItr != instanceMetrics_.End())
+    {
+        instanceSort.Push(instanceItr->second_);
+        instanceItr++;
+    }
+
+    Sort(instanceSort.Begin(), instanceSort.End(), CompareInstanceMetrics);
+
+    Vector<MetricsSnapshot::InstanceMetric>::ConstIterator instanceSortItr = instanceSort.Begin();
+
+    static const int NAME_MAX_LENGTH = 20;
+    static const int ENTRY_MAX_LENGTH = 128;
+    
+    char name[NAME_MAX_LENGTH];
+    char entry[ENTRY_MAX_LENGTH];
+
+    String line;
+
+    String header = "Class                 Total ( Native /  JS  /  C# )     ";
+
+    for (unsigned i = 0; i < columns; i++)
+        output += header;
+
+    output += "\n\n";
+
+    unsigned column = 0;
+
+    while (instanceSortItr != instanceSort.End())
+    {
+        const InstanceMetric& metric = *instanceSortItr;
+
+        if (metric.count < minCount)
+            continue;
+
+        snprintf(name, NAME_MAX_LENGTH, "%-20s", metric.classname.CString());
+        
+        // We use snprintf here as ToString doesn't seem to cover the unsigned %5u format
+        snprintf(entry, ENTRY_MAX_LENGTH, "%s : %5u ( %5u, %5u, %5u )", name , metric.count, metric.nativeInstances, metric.jsInstances, metric.netInstances);
+
+        if (columns == 1 || column == columns)
+        {
+            line += "\n";
+            output += line;
+            line.Clear();                        
+            column = 0;
+        }
+
+        line += entry;
+        column++;
+        line += "     ";
+
+        instanceSortItr++;
+    }
+
+    if (line.Length())
+        output += line;
+
+    return output;
+
+}
+
+Metrics::Metrics(Context* context) :
+    Object(context),
+    enabled_(false)
+{    
+    Metrics::metrics_ = this;
+}
+
+Metrics::~Metrics()
+{
+    Disable();
+    Metrics::metrics_ = 0;
+}
+
+void Metrics::CaptureInstances(MetricsSnapshot* snapshot)
+{
+    PruneExpiredInstances();
+
+    const String unkClassName("-Unknown Class-");
+
+    for (unsigned i = 0; i < instances_.Size(); i++)
+    {
+        RefCounted* r = instances_[i];
+
+        const String& name = r->GetTypeName();
+
+        MetricsSnapshot::InstanceMetric& metric = snapshot->instanceMetrics_[name];
+
+        metric.classname = name;
+        metric.count++;
+
+        if (r->GetInstantiationType() == INSTANTIATION_NATIVE)
+            metric.nativeInstances++;
+        else if (r->GetInstantiationType() == INSTANTIATION_JAVASCRIPT)
+            metric.jsInstances++;
+        else if (r->GetInstantiationType() == INSTANTIATION_NET)
+            metric.netInstances++;
+    }
+
+}
+
+void Metrics::Capture(MetricsSnapshot* snapshot)
+{
+    if (!enabled_)
+    {
+        ATOMIC_LOGERROR("Metrics::Capture - Metrics subsystem is not enabled");
+        return;
+    }
+
+    if (!snapshot)
+        return;
+
+    snapshot->Clear();
+
+    CaptureInstances(snapshot);
+
+}
+
+bool Metrics::Enable()
+{
+    if (enabled_)
+        return false;
+
+    if (everEnabled_)
+    {
+        ATOMIC_LOGERROR("Metrics::Enable - Metrics subsystem is not designed to be restarted");
+        return false;
+    }
+
+    enabled_ = everEnabled_ = true;
+
+    RefCounted::AddRefCountedCreatedFunction(Metrics::OnRefCountedCreated);
+    RefCounted::AddRefCountedDeletedFunction(Metrics::OnRefCountedDeleted);
+
+    return true;
+}
+
+void Metrics::Disable()
+{
+    if (!enabled_)
+        return;
+
+    enabled_ = false;
+
+    instances_.Clear();
+
+    RefCounted::RemoveRefCountedCreatedFunction(Metrics::OnRefCountedCreated);
+    RefCounted::RemoveRefCountedDeletedFunction(Metrics::OnRefCountedDeleted);
+}
+
+void Metrics::PruneExpiredInstances()
+{
+    Vector<WeakPtr<RefCounted>> cinstances = instances_;
+
+    instances_.Clear();
+
+    for (unsigned i = 0; i < cinstances.Size(); i++)
+    {
+        if (!cinstances[i].Expired())
+        {
+            instances_.Push(cinstances[i]);
+        }
+    }
+
+}
+
+void Metrics::OnRefCountedCreated(RefCounted* refCounted)
+{
+    if (!metrics_)
+    {
+        ATOMIC_LOGERROR("Metrics::OnRefCountedCreated - null instance");
+        return;
+    }
+
+    // We're called from the RefCounted constructor, so we don't know whether we're an object, etc
+    metrics_->instances_.Push(WeakPtr<RefCounted>(refCounted));
+
+    // prune expired whenever 8k boundry is crossed
+    if (!(metrics_->instances_.Size() % 8192))
+    {
+        metrics_->PruneExpiredInstances();
+    }
+
+}
+
+void Metrics::OnRefCountedDeleted(RefCounted* refCounted)
+{
+    if (!metrics_)
+    {
+        ATOMIC_LOGERROR("Metrics::OnRefCountedDeleted - null instance");
+        return;
+    }
+
+    Vector<WeakPtr<RefCounted>>::Iterator itr = metrics_->instances_.Find(WeakPtr<RefCounted>(refCounted));
+
+    if (itr != metrics_->instances_.End())
+    {
+        metrics_->instances_.Erase(itr);
+    }
+}
+
+
+}

+ 139 - 0
Source/Atomic/Metrics/Metrics.h

@@ -0,0 +1,139 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Core/Object.h"
+
+namespace Atomic
+{
+
+class MetricsSnapshot : public RefCounted
+{
+    friend class Metrics;
+
+    ATOMIC_REFCOUNTED(MetricsSnapshot)
+
+public:
+
+    MetricsSnapshot() {}
+
+    String PrintData(unsigned columns = 1, unsigned minCount = 0);
+
+    void Clear();
+
+private:
+
+    struct InstanceMetric
+    {
+        String classname;
+        unsigned count;
+        unsigned nativeInstances;
+        unsigned jsInstances;
+        unsigned netInstances;
+
+        InstanceMetric()
+        {
+            count = nativeInstances = jsInstances = netInstances = 0;
+        }
+
+    };
+
+    struct NodeMetric
+    {
+        String name;
+        unsigned count;
+
+        NodeMetric()
+        {
+            count = 0;
+        }
+    };
+
+    struct ResourceMetric
+    {
+        String name;
+        String group;
+        unsigned memoryUsage;
+
+        ResourceMetric()
+        {
+            memoryUsage = 0;
+        }
+    };
+
+    static bool CompareInstanceMetrics(const InstanceMetric& lhs, const InstanceMetric& rhs);
+
+    // StringHash(classname) => InstanceMetrics
+    HashMap<StringHash, InstanceMetric> instanceMetrics_;
+
+    // StringHash(node name) => NodeMetrics
+    HashMap<StringHash, NodeMetric> nodeMetrics_;
+
+    // StringHash(resource name) => ResourceMetrics
+    HashMap<StringHash, ResourceMetric> resourceMetrics_;
+
+};
+
+/// Metrics subsystem
+class ATOMIC_API Metrics : public Object
+{
+    friend class Application;
+
+    ATOMIC_OBJECT(Metrics, Object)
+
+public:
+
+    /// Construct.
+    Metrics(Context* context);
+    /// Destruct.
+    virtual ~Metrics();
+
+    bool Enable();
+
+    bool GetEnabled() const { return enabled_; }    
+
+    void Capture(MetricsSnapshot* snapshot);
+
+private:    
+
+    void Disable();
+
+    void CaptureInstances(MetricsSnapshot* snapshot);
+
+    void PruneExpiredInstances();
+
+    static void OnRefCountedCreated(RefCounted* refCounted);
+    static void OnRefCountedDeleted(RefCounted* refCounted);
+
+    static Metrics* metrics_;
+
+    static bool everEnabled_;
+
+    bool enabled_;
+
+    Vector<WeakPtr<RefCounted>> instances_;
+
+};
+
+
+}

+ 64 - 1
Source/Atomic/UI/SystemUI/DebugHud.cpp

@@ -34,6 +34,8 @@
 
 #include "../../DebugNew.h"
 
+#include "../../Metrics/Metrics.h"
+
 namespace Atomic
 {
 
@@ -60,6 +62,7 @@ static const float FPS_UPDATE_INTERVAL = 0.5f;
 DebugHud::DebugHud(Context* context) :
     Object(context),
     profilerMaxDepth_(M_MAX_UNSIGNED),
+    profilerMode_(DEBUG_HUD_PROFILE_PERFORMANCE),
     profilerInterval_(1000),
     useRendererStats_(true),
     mode_(DEBUGHUD_SHOW_NONE),
@@ -94,6 +97,10 @@ DebugHud::DebugHud(Context* context) :
     layout_->AddChild(profilerText_);
 
     SubscribeToEvent(E_POSTUPDATE, ATOMIC_HANDLER(DebugHud, HandlePostUpdate));
+
+    statsText_->SetTextEffect(TE_SHADOW);
+    modeText_->SetTextEffect(TE_SHADOW);
+    profilerText_->SetTextEffect(TE_SHADOW);
 }
 
 DebugHud::~DebugHud()
@@ -207,6 +214,7 @@ void DebugHud::Update(float timeStep)
     }
 
     Profiler* profiler = GetSubsystem<Profiler>();
+    
     if (profiler)
     {
         if (profilerTimer_.GetMSec(false) >= profilerInterval_)
@@ -215,7 +223,32 @@ void DebugHud::Update(float timeStep)
 
             if (profilerText_->IsVisible())
             {
-                String profilerOutput = profiler->PrintData(false, false, profilerMaxDepth_);
+                String profilerOutput;
+
+                if (profilerMode_ == DEBUG_HUD_PROFILE_PERFORMANCE)
+                {
+                    profilerOutput = profiler->PrintData(false, false, profilerMaxDepth_);
+                }
+                else
+                {
+                    Metrics* metrics = GetSubsystem<Metrics>();
+
+                    if (metrics)
+                    {
+                        if (!metrics->GetEnabled())
+                            metrics->Enable();
+
+                        SharedPtr<MetricsSnapshot> snapshot(new MetricsSnapshot());
+                        metrics->Capture(snapshot);
+                        profilerOutput = snapshot->PrintData(2);
+                    }
+                    else
+                    {
+                        profilerOutput = "Metrics subsystem not found";
+                    }
+
+                }
+
                 profilerText_->SetText(profilerOutput);
             }
 
@@ -224,6 +257,36 @@ void DebugHud::Update(float timeStep)
     }
 }
 
+void DebugHud::SetProfilerMode(DebugHudProfileMode mode)
+{ 
+    profilerMode_ = mode; 
+
+    if (profilerMode_ == DEBUG_HUD_PROFILE_PERFORMANCE)
+    {
+        if (profilerText_.NotNull())
+        {
+            profilerText_->SetText("");
+            profilerText_->SetFont(profilerText_->GetFont(), 11);
+        }
+    }
+    else
+    {
+        int size = 8;
+
+        Metrics* metrics = GetSubsystem<Metrics>();
+
+        if (!metrics)
+            size = 32;
+
+        if (profilerText_.NotNull())
+        {
+            profilerText_->SetText("");
+            profilerText_->SetFont(profilerText_->GetFont(), size);
+        }
+    }
+
+}
+
 void DebugHud::SetDefaultStyle(XMLFile* style)
 {
     if (!style)

+ 9 - 0
Source/Atomic/UI/SystemUI/DebugHud.h

@@ -25,6 +25,8 @@
 #include "../../Core/Object.h"
 #include "../../Core/Timer.h"
 
+#include "../UIEnums.h"
+
 namespace Atomic
 {
 
@@ -67,6 +69,8 @@ public:
     void SetProfilerMaxDepth(unsigned depth);
     /// Set profiler accumulation interval in seconds.
     void SetProfilerInterval(float interval);
+    /// Set the profiler mode to either performance or metrics
+    void SetProfilerMode(DebugHudProfileMode mode);
     /// Set whether to show 3D geometry primitive/batch count only. Default false.
     void SetUseRendererStats(bool enable);
     /// Toggle elements.
@@ -89,6 +93,9 @@ public:
     /// Return currently shown elements.
     unsigned GetMode() const { return mode_; }
 
+    /// Return the profiler mode (performance, metrics, etc)
+    DebugHudProfileMode GetProfilerMode() const { return profilerMode_; }
+
     /// Return maximum profiler block depth.
     unsigned GetProfilerMaxDepth() const { return profilerMaxDepth_; }
 
@@ -128,6 +135,8 @@ private:
     Timer profilerTimer_;
     /// Profiler max block depth.
     unsigned profilerMaxDepth_;
+    /// Profiler mode 
+    DebugHudProfileMode profilerMode_;
     /// Profiler accumulation interval.
     unsigned profilerInterval_;
     /// Show 3D geometry primitive/batch count flag.

+ 1 - 1
Source/Atomic/UI/SystemUI/Text.cpp

@@ -193,7 +193,7 @@ void Text::GetBatches(PODVector<SystemUIBatch>& batches, PODVector<float>& verte
             break;
 
         case TE_SHADOW:
-            ConstructBatch(pageBatch, pageGlyphLocation, 1, 1, &effectColor_, effectDepthBias_);
+            ConstructBatch(pageBatch, pageGlyphLocation, 2, 2, &effectColor_, effectDepthBias_);
             ConstructBatch(pageBatch, pageGlyphLocation, 0, 0);
             break;
 

+ 10 - 0
Source/Atomic/UI/UI.cpp

@@ -917,6 +917,16 @@ void UI::SetDebugHudExtents(bool useRootExtent, const IntVector2& position, cons
 
 }
 
+void UI::SetDebugHudProfileMode(DebugHudProfileMode mode)
+{
+    SystemUI::DebugHud* hud = GetSubsystem<SystemUI::DebugHud>();
+
+    if (!hud)
+        return;
+
+    hud->SetProfilerMode(mode);
+}
+
 void UI::ShowConsole(bool value)
 {
     SystemUI::Console* console = GetSubsystem<SystemUI::Console>();

+ 6 - 0
Source/Atomic/UI/UI.h

@@ -25,6 +25,7 @@
 #include <ThirdParty/TurboBadger/tb_widgets_listener.h>
 
 #include "../Core/Object.h"
+#include "../UI/UIEnums.h"
 #include "../UI/UIBatch.h"
 
 namespace Atomic
@@ -93,9 +94,14 @@ public:
     void GetTBIDString(unsigned id, String& value);
 
     SystemUI::MessageBox *ShowSystemMessageBox(const String& title, const String& message);
+
+    // Debug HUD
+
     void ShowDebugHud(bool value);
     void ToggleDebugHud();
 
+    void SetDebugHudProfileMode(DebugHudProfileMode mode);
+
     void SetDebugHudExtents(bool useRootExtents = true, const IntVector2& position = IntVector2::ZERO, const IntVector2& size = IntVector2::ZERO);
 
     void ShowConsole(bool value);

+ 33 - 0
Source/Atomic/UI/UIEnums.h

@@ -0,0 +1,33 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+namespace Atomic
+{
+    enum DebugHudProfileMode
+    {
+        DEBUG_HUD_PROFILE_PERFORMANCE,
+        DEBUG_HUD_PROFILE_METRICS
+    };
+
+}

+ 5 - 0
Source/AtomicApp/AppBase.cpp

@@ -69,6 +69,11 @@ namespace Atomic
 
     }
 
+    void AppBase::AddArgument(const String& argument)
+    {
+        arguments_.Push(argument);
+    }
+
     void AppBase::ProcessArguments()
     {
         for (unsigned i = 0; i < arguments_.Size(); ++i)

+ 2 - 1
Source/AtomicApp/AppBase.h

@@ -50,7 +50,8 @@ namespace Atomic
 
         virtual void Shutdown() { Stop(); }
 
-        static void AddArgument(const String& argument) { arguments_.Push(argument); }
+        /// Called before initializing application for inserting arguments
+        static void AddArgument(const String& argument);
 
         virtual void ProcessArguments();
 

+ 3 - 0
Source/AtomicApp/Player/IPCPlayerApp.cpp

@@ -215,7 +215,10 @@ namespace Atomic
 
         SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
         if (debugHud)
+        {
             debugHud->SetMode(eventData["debugHudMode"].GetUInt());
+            debugHud->SetProfilerMode((DebugHudProfileMode)eventData["debugHudProfilerMode"].GetUInt());
+        }
 
     }
 

+ 13 - 0
Source/AtomicEditor/EditorMode/AEEditorMode.cpp

@@ -69,6 +69,7 @@ void EditorMode::HandleIPCWorkerStarted(StringHash eventType, VariantMap& eventD
     SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
 
     startupData["debugHudMode"] = debugHud ? debugHud->GetMode() : (unsigned) 0;
+    startupData["debugHudProfilerMode"] = (unsigned) (debugHud ? debugHud->GetProfilerMode() : DEBUG_HUD_PROFILE_PERFORMANCE);
 
     SendEvent(E_EDITORPLAYREQUEST);
 
@@ -261,6 +262,18 @@ bool EditorMode::PlayProjectInternal(const String &addArgs, bool debug)
     if (debug)
         vargs.Insert(0, "--debug");
 
+    // If the debug hud up is up with metrics info, pass the --autometrics to player
+    SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
+
+    if (debugHud)
+    {
+        if (debugHud->GetMode() & Atomic::SystemUI::DEBUGHUD_SHOW_PROFILER)
+        {
+            if (debugHud->GetProfilerMode() == DEBUG_HUD_PROFILE_METRICS)
+                vargs.Insert(0, "--autometrics");
+        }
+    }
+
     if (addArgs.Length() > 0)
         vargs.Insert(0, addArgs.Split(' '));
 

+ 0 - 3
Source/AtomicEditor/Editors/SceneEditor3D/SceneEditor3D.cpp

@@ -121,9 +121,6 @@ SceneEditor3D::SceneEditor3D(Context* context, const String &fullpath, UITabCont
     IntRect rect = container_->GetContentRoot()->GetRect();
     rootContentWidget_->SetSize(rect.Width(), rect.Height());
 
-    // FIXME: This sets the debug hud to render over the scene editor, when a scene is open, which is generally more legible. This should be generalized
-    GetSubsystem<UI>()->SetDebugHudExtents(false, container_->GetContentRoot()->ConvertToRoot(IntVector2(rect.left_, rect.top_)), IntVector2(rect.Width(), rect.Height()));
-
     SubscribeToEvent(E_PROJECTUSERPREFSAVED, ATOMIC_HANDLER(SceneEditor3D, HandleUserPrefSaved));
 
     SubscribeToEvent(scene_, E_SCENEEDITNODECREATED, ATOMIC_HANDLER(SceneEditor3D, HandleSceneEditNodeCreated));

+ 3 - 0
Source/AtomicEditor/PlayerMode/AEPlayerMode.cpp

@@ -96,7 +96,10 @@ void PlayerMode::HandleIPCInitialize(StringHash eventType, VariantMap& eventData
 
     SystemUI::DebugHud* debugHud = GetSubsystem<SystemUI::DebugHud>();
     if (debugHud)
+    {
         debugHud->SetMode(eventData["debugHudMode"].GetUInt());
+        debugHud->SetProfilerMode((DebugHudProfileMode) eventData["debugHudProfilerMode"].GetUInt());
+    }
 
 }
 

+ 4 - 0
Source/AtomicJS/Javascript/JSAtomic.cpp

@@ -30,6 +30,7 @@
 #include <Atomic/Engine/Engine.h>
 #include <Atomic/Audio/Audio.h>
 #include <Atomic/UI/UI.h>
+#include <Atomic/Metrics/Metrics.h>
 
 #ifdef ATOMIC_NETWORK
 #include <Atomic/Network/Network.h>
@@ -397,6 +398,9 @@ void jsapi_init_atomic(JSVM* vm)
     js_push_class_object_instance(ctx, vm->GetSubsystem<Input>(), "Input");
     duk_put_prop_string(ctx, -2, "input");
 
+    js_push_class_object_instance(ctx, vm->GetSubsystem<Metrics>(), "Metrics");
+    duk_put_prop_string(ctx, -2, "metrics");
+
     duk_push_c_function(ctx, js_atomic_GetFileSystem, 0);
     duk_put_prop_string(ctx, -2, "getFileSystem");
 

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

@@ -73,14 +73,7 @@ namespace Atomic
 
         ATOMIC_EXPORT_API const char* csi_Atomic_RefCounted_GetTypeName(RefCounted* self)
         {
-            if (!self->IsObject())
-            {
-                return "RefCounted";
-            }
-
-            static String returnValue;
-            returnValue = ((Object*)self)->GetTypeName();
-            return returnValue.CString();
+            return self ? self->GetTypeName().CString() : "(NULL)";
         }
 
         ATOMIC_EXPORT_API int csi_Atomic_RefCounted_Refs(RefCounted* self)

+ 1 - 1
Source/AtomicNET/NETScript/CSComponentAssembly.cpp

@@ -137,7 +137,7 @@ namespace Atomic
 
                     while (itr != factories.End())
                     {
-                        if (itr->second_->GetTypeName() == typeName)
+                        if (itr->second_->GetFactoryTypeName() == typeName)
                         {
                             varType = VAR_RESOURCEREF;
                             break;

+ 69 - 12
Source/AtomicPlayer/Player.cpp

@@ -86,29 +86,86 @@ Scene* Player::LoadScene(const String& filename, Camera *camera)
     eventData[PlayerSceneLoadEnd::P_SUCCESS] = true;
     scene->SendEvent(E_PLAYERSCENELOADEND, eventData);
 
+    loadedScenes_.Push(SharedPtr<Scene>(scene));
+
     if (currentScene_.Null())
     {
-        currentScene_ = scene;
+        SetCurrentScene(scene, camera);
+    }
+
+    return scene;
+}
 
-        if(!camera)
+void Player::SetCurrentScene(Scene* scene, Camera* camera)
+{
+    Vector<SharedPtr<Scene>>::ConstIterator citr = loadedScenes_.Find(SharedPtr<Scene>(scene));
+
+    if (citr == loadedScenes_.End())
+    {
+        ATOMIC_LOGERROR("Player::UnloadScene - unknown scene");
+        return;
+    }
+
+    currentScene_ = scene;
+
+    if (!camera)
+    {
+        PODVector<Node*> cameraNodes;
+        scene->GetChildrenWithComponent(cameraNodes, Camera::GetTypeStatic(), true);
+        if (cameraNodes.Size())
         {
-            PODVector<Node*> cameraNodes;
-            scene->GetChildrenWithComponent(cameraNodes, Camera::GetTypeStatic(), true);
-            if (cameraNodes.Size())
-            {
-                camera = cameraNodes[0]->GetComponent<Camera>();
-            }
+            camera = cameraNodes[0]->GetComponent<Camera>();
         }
+    }
+
+    viewport_->SetScene(scene);
+
+    if (camera)
+        viewport_->SetCamera(camera);
+
+}
 
-        viewport_->SetScene(scene);
+void Player::UnloadScene(Scene* scene)
+{
+    SharedPtr<Scene> keepalive(scene);
 
-        if (camera)
-            viewport_->SetCamera(camera);
+    if (currentScene_ == scene)
+    {
+        viewport_->SetScene(0);
+        viewport_->SetCamera(0);
+        currentScene_ = 0;
+    }
 
+    Vector<SharedPtr<Scene>>::ConstIterator citr = loadedScenes_.Find(keepalive);
+    
+    if (citr == loadedScenes_.End())
+    {
+        ATOMIC_LOGERROR("Player::UnloadScene - unknown scene");
+        return;
     }
 
-    return scene;
+    VariantMap eventData;
+    eventData[PlayerSceneUnload::P_SCENE] = scene;
+
+    scene->SendEvent(E_PLAYERSCENEUNLOAD, eventData);
+
+    loadedScenes_.Remove(keepalive);
+
 }
 
+void Player::UnloadAllScenes()
+{
+    Vector<SharedPtr<Scene>> scenes = loadedScenes_;
+
+    for (unsigned i = 0; i < scenes.Size(); i++)
+    {
+        UnloadScene(scenes[i]);
+    }
+
+    assert(loadedScenes_.Size() == 0);
+    
+}
+
+
 
 }

+ 17 - 1
Source/AtomicPlayer/Player.h

@@ -42,9 +42,23 @@ public:
     /// Destruct.
     virtual ~Player();
 
+    /// Load a scene file with optional camera specified
     Scene* LoadScene(const String& filename, Camera* camera = NULL);
 
-    Scene* GetCurrentScene() { return currentScene_; }
+    /// Get the number of currently loaded scenes
+    unsigned GetNumScenes() { return loadedScenes_.Size();  }
+
+    /// Get the scene loaded at specified index 
+    Scene* GetScene(unsigned index) { return loadedScenes_[index]; }
+
+    /// Set the current scene 
+    void SetCurrentScene(Scene* scene, Camera* camera = NULL);
+
+    /// Unload a scene
+    void UnloadScene(Scene* scene);
+
+    /// Unload all loaded scenes
+    void UnloadAllScenes();    
 
 private:
 
@@ -53,6 +67,8 @@ private:
     // Strong reference
     SharedPtr<Scene> currentScene_;
 
+    Vector<SharedPtr<Scene>> loadedScenes_;
+
     SharedPtr<Viewport> viewport_;
 
 };

+ 5 - 0
Source/AtomicPlayer/PlayerEvents.h

@@ -39,4 +39,9 @@ namespace AtomicPlayer
         ATOMIC_PARAM(P_SUCCESS, Success);   // bool
     }
 
+    ATOMIC_EVENT(E_PLAYERSCENEUNLOAD, PlayerSceneUnload)
+    {
+        ATOMIC_PARAM(P_SCENE, Scene);       // Scene
+    }
+
 }