Переглянути джерело

Added an Application base class which can be optionally used to execute an engine main loop, and perform initialization/shutdown.
Modified Urho3D, CharacterDemo & HelloWorld to use the Application class.
Modified engine initialization so that subsystems are created before initialization where possible.
Modified Renderer to not cache the ResourceCache pointer to prevent crash depending on event handling order when setting the screen mode.

Lasse Öörni 12 роки тому
батько
коміт
dc7898ba1f

+ 71 - 0
Engine/Engine/Application.cpp

@@ -0,0 +1,71 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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 "Precompiled.h"
+#include "Application.h"
+#include "Engine.h"
+#include "ProcessUtils.h"
+
+#include <exception>
+
+#include "DebugNew.h"
+
+Application::Application(Context* context) :
+    Object(context)
+{
+    engineParameters_ = Engine::ParseParameters(GetArguments());
+
+    // Create the Engine, but do not initialize it yet. Subsystems except Graphics & Renderer are registered at this point.
+    engine_ = new Engine(context);
+}
+
+Application::~Application()
+{
+}
+
+int Application::Run()
+{
+    try
+    {
+        int exitCode = Setup();
+        if (exitCode)
+            return exitCode;
+
+        if (!engine_->Initialize(engineParameters_))
+            return EXIT_FAILURE;
+
+        exitCode = Start();
+        if (exitCode)
+            return exitCode;
+
+        while (!engine_->IsExiting())
+            engine_->RunFrame();
+
+        exitCode = Stop();
+        return exitCode;
+    }
+    catch (std::bad_alloc&)
+    {
+        ErrorDialog(GetTypeName(), "An out-of-memory error occurred. The application will now exit.");
+        return EXIT_FAILURE;
+    }
+}

+ 74 - 0
Engine/Engine/Application.h

@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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 "Main.h"
+#include "Object.h"
+
+namespace Urho3D
+{
+
+class Engine;
+
+/// Base class for creating applications which initialize the Urho3D engine and run a main loop until exited.
+class Application : public Object
+{
+    OBJECT(Application);
+    
+public:
+    /// Construct. Parse default engine parameters from the command line, and create the engine in an uninitialized state.
+    Application(Context* context);
+
+    /// Destruct.
+    ~Application();
+
+    /// Setup before engine initialization. This is a chance to eg. modify the engine parameters. Return nonzero to terminate with an error exit code without initializing the engine.
+    virtual int Setup() { return 0; }
+
+    /// Startup after engine initialization and before running the main loop. Return nonzero to terminate with an error exit code without running the main loop.
+    virtual int Start() { return 0; }
+
+    /// Cleanup after the main loop. Return the exit code for the application.
+    virtual int Stop() { return 0; }
+
+    /// Initialize the engine and run the main loop, then return the application exit code. Catch out-of-memory exceptions while running.
+    int Run();
+
+protected:
+    /// Urho3D engine.
+    SharedPtr<Engine> engine_;
+    /// Engine parameters map.
+    VariantMap engineParameters_;
+};
+
+// Macro for defining a main function which creates a Context and the application, then runs it
+#define DEFINE_APPLICATION_MAIN(className) \
+int RunApplication() \
+{ \
+    SharedPtr<Context> context(new Context()); \
+    SharedPtr<className> application(new className(context)); \
+    return application->Run(); \
+} \
+DEFINE_MAIN(RunApplication());
+
+}

+ 43 - 51
Engine/Engine/Engine.cpp

@@ -90,6 +90,30 @@ Engine::Engine(Context* context) :
     headless_(false),
     audioPaused_(false)
 {
+    // Register self as a subsystem
+    context_->RegisterSubsystem(this);
+    
+    // Create subsystems which do not depend on engine initialization or startup parameters
+    context_->RegisterSubsystem(new Time(context_));
+    context_->RegisterSubsystem(new WorkQueue(context_));
+    #ifdef ENABLE_PROFILING
+    context_->RegisterSubsystem(new Profiler(context_));
+    #endif
+    context_->RegisterSubsystem(new FileSystem(context_));
+    #ifdef ENABLE_LOGGING
+    context_->RegisterSubsystem(new Log(context_));
+    #endif
+    context_->RegisterSubsystem(new ResourceCache(context_));
+    context_->RegisterSubsystem(new Network(context_));
+    context_->RegisterSubsystem(new Input(context_));
+    context_->RegisterSubsystem(new Audio(context_));
+    context_->RegisterSubsystem(new UI(context_));
+    
+    // Register object factories for libraries which are not automatically registered along with subsystem creation
+    RegisterSceneLibrary(context_);
+    RegisterPhysicsLibrary(context_);
+    RegisterNavigationLibrary(context_);
+    
     SubscribeToEvent(E_EXITREQUESTED, HANDLER(Engine, HandleExitRequested));
 }
 
@@ -102,13 +126,29 @@ bool Engine::Initialize(const VariantMap& parameters)
     if (initialized_)
         return true;
     
+    PROFILE(InitEngine);
+    
     // Set headless mode
     headless_ = GetParameter(parameters, "Headless", false).GetBool();
     
-    // Register subsystems and object factories
-    RegisterSubsystems();
+    // Register the rest of the subsystems
+    if (!headless_)
+    {
+        context_->RegisterSubsystem(new Graphics(context_));
+        context_->RegisterSubsystem(new Renderer(context_));
+    }
+    else
+    {
+        // Register graphics library objects explicitly in headless mode to allow them to work without using actual GPU resources
+        RegisterGraphicsLibrary(context_);
+    }
     
-    PROFILE(InitEngine);
+    // In debug mode, check now that all factory created objects can be created without crashing
+    #ifdef _DEBUG
+    const HashMap<ShortStringHash, SharedPtr<ObjectFactory> >& factories = context_->GetObjectFactories();
+    for (HashMap<ShortStringHash, SharedPtr<ObjectFactory> >::ConstIterator i = factories.Begin(); i != factories.End(); ++i)
+        SharedPtr<Object> object = i->second_->CreateObject();
+    #endif
     
     // Start logging
     Log* log = GetSubsystem<Log>();
@@ -632,54 +672,6 @@ const Variant& Engine::GetParameter(const VariantMap& parameters, const String&
     return i != parameters.End() ? i->second_ : defaultValue;
 }
 
-void Engine::RegisterSubsystems()
-{
-    // Register self as a subsystem
-    context_->RegisterSubsystem(this);
-    
-    // Create and register the rest of the subsystems. They will register object factories for their own libraries
-    context_->RegisterSubsystem(new Time(context_));
-    context_->RegisterSubsystem(new WorkQueue(context_));
-    #ifdef ENABLE_PROFILING
-    context_->RegisterSubsystem(new Profiler(context_));
-    #endif
-    context_->RegisterSubsystem(new FileSystem(context_));
-    context_->RegisterSubsystem(new ResourceCache(context_));
-    context_->RegisterSubsystem(new Network(context_));
-    
-    if (!headless_)
-    {
-        context_->RegisterSubsystem(new Graphics(context_));
-        context_->RegisterSubsystem(new Renderer(context_));
-    }
-    else
-    {
-        // Register Graphics library object factories also in headless mode; the objects will function without allocating
-        // actual GPU resources
-        RegisterGraphicsLibrary(context_);
-    }
-    
-    context_->RegisterSubsystem(new Input(context_));
-    context_->RegisterSubsystem(new UI(context_));
-    context_->RegisterSubsystem(new Audio(context_));
-    #ifdef ENABLE_LOGGING
-    context_->RegisterSubsystem(new Log(context_));
-    #endif
-    
-    // Scene, Physics & Navigation libraries do not have a corresponding subsystem which would register their object factories.
-    // Register manually now
-    RegisterSceneLibrary(context_);
-    RegisterPhysicsLibrary(context_);
-    RegisterNavigationLibrary(context_);
-    
-    // In debug mode, check that all factory created objects can be created without crashing
-    #ifdef _DEBUG
-    const HashMap<ShortStringHash, SharedPtr<ObjectFactory> >& factories = context_->GetObjectFactories();
-    for (HashMap<ShortStringHash, SharedPtr<ObjectFactory> >::ConstIterator i = factories.Begin(); i != factories.End(); ++i)
-        SharedPtr<Object> object = i->second_->CreateObject();
-    #endif
-}
-
 void Engine::HandleExitRequested(StringHash eventType, VariantMap& eventData)
 {
     if (autoExit_)

+ 0 - 2
Engine/Engine/Engine.h

@@ -101,8 +101,6 @@ public:
     static const Variant& GetParameter(const VariantMap& parameters, const String& parameter, const Variant& defaultValue = Variant::EMPTY);
     
 private:
-    /// Create and register subsystems. In headless mode graphics, input & UI are not created.
-    void RegisterSubsystems();
     /// Handle exit requested event.
     void HandleExitRequested(StringHash eventType, VariantMap& eventData);
     

+ 17 - 14
Engine/Graphics/Renderer.cpp

@@ -282,6 +282,12 @@ Renderer::Renderer(Context* context) :
     shadersDirty_(true),
     initialized_(false)
 {
+    #ifndef USE_OPENGL
+    shaderPath_ = "Shaders/HLSL/";
+    #else
+    shaderPath_ = "Shaders/GLSL/";
+    #endif
+
     SubscribeToEvent(E_SCREENMODE, HANDLER(Renderer, HandleScreenMode));
     SubscribeToEvent(E_GRAPHICSFEATURES, HANDLER(Renderer, HandleGraphicsFeatures));
     
@@ -1050,6 +1056,7 @@ ShaderVariation* Renderer::GetShader(ShaderType type, const String& name, bool c
     if (name.Trimmed().Empty())
         return 0;
     
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
     String shaderName = shaderPath_;
     String variationName;
     
@@ -1064,11 +1071,11 @@ ShaderVariation* Renderer::GetShader(ShaderType type, const String& name, bool c
     
     if (checkExists)
     {
-        if (!cache_->Exists(shaderName))
+        if (!cache->Exists(shaderName))
             return 0;
     }
     
-    Shader* shader = cache_->GetResource<Shader>(shaderName);
+    Shader* shader = cache->GetResource<Shader>(shaderName);
     if (shader)
         return shader->GetVariation(type, variationName);
     else
@@ -1463,13 +1470,6 @@ void Renderer::Initialize()
     PROFILE(InitRenderer);
     
     graphics_ = graphics;
-    cache_ = cache;
-    
-    #ifndef USE_OPENGL
-    shaderPath_ = "Shaders/HLSL/";
-    #else
-    shaderPath_ = "Shaders/GLSL/";
-    #endif
     
     if (!graphics_->GetShadowMapFormat())
         drawShadows_ = false;
@@ -1598,8 +1598,10 @@ void Renderer::LoadPassShaders(Technique* tech, StringHash type)
 
 void Renderer::ReleaseMaterialShaders()
 {
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
     PODVector<Material*> materials;
-    cache_->GetResources<Material>(materials);
+    
+    cache->GetResources<Material>(materials);
     
     for (unsigned i = 0; i < materials.Size(); ++i)
         materials[i]->ReleaseShaders();
@@ -1607,15 +1609,16 @@ void Renderer::ReleaseMaterialShaders()
 
 void Renderer::ReloadTextures()
 {
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
     PODVector<Resource*> textures;
     
-    cache_->GetResources(textures, Texture2D::GetTypeStatic());
+    cache->GetResources(textures, Texture2D::GetTypeStatic());
     for (unsigned i = 0; i < textures.Size(); ++i)
-        cache_->ReloadResource(textures[i]);
+        cache->ReloadResource(textures[i]);
     
-    cache_->GetResources(textures, TextureCube::GetTypeStatic());
+    cache->GetResources(textures, TextureCube::GetTypeStatic());
     for (unsigned i = 0; i < textures.Size(); ++i)
-        cache_->ReloadResource(textures[i]);
+        cache->ReloadResource(textures[i]);
 }
 
 void Renderer::CreateGeometries()

+ 0 - 2
Engine/Graphics/Renderer.h

@@ -379,8 +379,6 @@ private:
     
     /// Graphics subsystem.
     WeakPtr<Graphics> graphics_;
-    /// Resource cache subsystem.
-    WeakPtr<ResourceCache> cache_;
     /// Default renderpath.
     SharedPtr<RenderPath> defaultRenderPath_;
     /// Default zone.

+ 22 - 0
Extras/CharacterDemo/Character.h

@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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 "Component.h"

+ 7 - 23
Extras/CharacterDemo/CharacterDemo.cpp

@@ -32,29 +32,10 @@
 const float CAMERA_MIN_DIST = 1.0f;
 const float CAMERA_MAX_DIST = 5.0f;
 
-int Run()
-{
-    SharedPtr<Context> context(new Context());
-    SharedPtr<Engine> engine(new Engine(context));
-    
-    VariantMap engineParameters = Engine::ParseParameters(GetArguments());
-    engineParameters["WindowTitle"] = "CharacterDemo";
-    engineParameters["LogName"] = "CharacterDemo.log";
-    
-    engine->Initialize(engineParameters);
-    
-    SharedPtr<CharacterDemo> characterDemo(new CharacterDemo(context));
-    characterDemo->Start();
-    while (!engine->IsExiting())
-        engine->RunFrame();
-    
-    return 0;
-}
-
-DEFINE_MAIN(Run())
+DEFINE_APPLICATION_MAIN(CharacterDemo)
 
 CharacterDemo::CharacterDemo(Context* context) :
-    Object(context),
+    Application(context),
     cache_(GetSubsystem<ResourceCache>()),
     firstPerson_(false)
 {
@@ -62,11 +43,14 @@ CharacterDemo::CharacterDemo(Context* context) :
     context_->RegisterFactory<Character>();
 }
 
-void CharacterDemo::Start()
+int CharacterDemo::Start()
 {
     CreateScene();
     CreateCharacter();
     SubscribeToEvents();
+
+    // Go on to the main loop
+    return EXIT_SUCCESS;
 }
 
 void CharacterDemo::CreateScene()
@@ -222,7 +206,7 @@ void CharacterDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
     Input* input = GetSubsystem<Input>();
 
     if (input->GetKeyDown(KEY_ESC))
-        GetSubsystem<Engine>()->Exit();
+        engine_->Exit();
     
     if (input->GetKeyPress('F'))
         firstPerson_ = !firstPerson_;

+ 26 - 4
Extras/CharacterDemo/CharacterDemo.h

@@ -1,6 +1,28 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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 "Object.h"
+#include "Application.h"
 
 using namespace Urho3D;
 
@@ -13,7 +35,7 @@ namespace Urho3D
 
 class Character;
 
-class CharacterDemo : public Object
+class CharacterDemo : public Application
 {
     OBJECT(CharacterDemo);
 
@@ -21,8 +43,8 @@ public:
     /// Construct.
     CharacterDemo(Context* context);
     
-    /// Initialize.
-    void Start();
+    /// Startup after engine initialization and before running the main loop.
+    virtual int Start();
     
 private:
     /// Create static scene content.

+ 14 - 5
Samples/01_HelloWorld/HelloWorld.cpp

@@ -30,21 +30,30 @@
 #include "UI.h"
 
 #include "HelloWorld.h"
-#include "Run.h"
 
 // Expands to this example's entry-point
-DEFINE_MAIN(Run<HelloWorld>())
-
+DEFINE_APPLICATION_MAIN(HelloWorld)
 
 HelloWorld::HelloWorld(Context* context) :
-    Object(context),
+    Application(context),
     cache_(GetSubsystem<ResourceCache>())
+{
+    // Modify engine startup parameters
+    engineParameters_["WindowTitle"] = GetTypeName();
+    engineParameters_["LogName"]     = GetTypeName() + ".log";
+    engineParameters_["FullScreen"]  = false;
+}
+
+int HelloWorld::Start()
 {
     // Create "Hello World" Text
     CreateText();
 
     // Finally, hook-up this HelloWorld instance to handle update events
     SubscribeToEvents();
+
+    // Go on to the main loop
+    return EXIT_SUCCESS;
 }
 
 void HelloWorld::CreateText()
@@ -79,6 +88,6 @@ void HelloWorld::HandleUpdate(StringHash eventType, VariantMap& eventData)
     if (GetSubsystem<Input>()->GetKeyPress(KEY_ESC))
     {
         // ...if so, request Engine exit
-        GetSubsystem<Engine>()->Exit();
+        engine_->Exit();
     }
 }

+ 6 - 3
Samples/01_HelloWorld/HelloWorld.h

@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include "Object.h"
+#include "Application.h"
 #include "ResourceCache.h"
 
 // All Urho3D classes reside in namespace Urho3D
@@ -33,15 +33,18 @@ using namespace Urho3D;
 ///     - Initialization of the Urho3D engine;
 ///     - Adding a Text element to the graphical user interface;
 ///     - Subscribing to and handling of update events;
-class HelloWorld : public Object
+class HelloWorld : public Application
 {
     // Mandatory when deriving from Object, enables type information
     OBJECT(HelloWorld)
 
 public:
-    /// Construct.
+    /// Construct. Note that Engine is not yet initialized at this point, so must defer actual startup for later.
     HelloWorld(Context* context);
 
+    /// Startup after engine initialization.
+    virtual int Start();
+
 private:
     /// Constructs a new Text instance, containing the 'Hello World' String, and
     /// adds it to the UI root element.

+ 0 - 71
Samples/Run.h

@@ -1,71 +0,0 @@
-//
-// Copyright (c) 2008-2013 the Urho3D project.
-//
-// 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 "Context.h"
-#include "Engine.h"
-
-#include "Main.h"
-#include "DebugNew.h"
-
-/// Templated entry-point common to all samples. This function expects T to be a
-/// subclass of Object, whose constructor performs any and all setup code
-/// necessary to run.
-/// After instantiating said subclass, this function will enter a main loop and
-/// kee[ running for as long as while Engine exit has not been requested.
-/// Consequently, any such subclass _must_ provide a means to request engine
-/// shutdown. Failure to do so will result in a program that can't be
-/// stopped graciously and has to be killed by the user.
-///
-/// Usage:  simply add the line \code DEFINE_MAIN(Run<T>()) \endcode
-///         in the subclass' implementation, where T equals subclass type.
-template<class T> int Run()
-{
-    // Construct a new execution Context
-    SharedPtr<Context> context(new Context());
-    // Construct the Urho3D engine, setting up other subsystems in the process
-    SharedPtr<Engine> engine(new Engine(context));
-
-    // Let engine parse command-line parameters, set window title and log file
-    // to use based on specified template's typename
-    VariantMap engineParameters = Engine::ParseParameters(GetArguments());
-    String typeName(T::GetTypeNameStatic());
-
-    engineParameters["WindowTitle"] = typeName;
-    engineParameters["LogName"]     = typeName.Append(".log");
-    engineParameters["FullScreen"]  = false;
-
-    // Initialize the Engine according to desired parameters
-    engine->Initialize(engineParameters);
-
-    // Construct Sample instance
-    SharedPtr<T> t(new T(context));
-
-    // Run main loop while the engine is not exiting
-    while (!engine->IsExiting())
-        engine->RunFrame();
-
-    return EXIT_SUCCESS;
-}
-
-

+ 76 - 99
Urho3D/Urho3D.cpp

@@ -30,57 +30,23 @@
 #include "ResourceEvents.h"
 #include "Script.h"
 #include "ScriptFile.h"
+#include "Urho3D.h"
 
 #ifdef ENABLE_LUA
 #include "LuaScript.h"
 #endif
 
-#include <exception>
-
 #include "DebugNew.h"
 
-using namespace Urho3D;
-
-class Application : public Object
-{
-    OBJECT(Application);
-    
-public:
-    Application(Context* context);
-    int Run();
-    
-private:
-    void ErrorExit();
-    void HandleScriptReloadStarted(StringHash eventType, VariantMap& eventData);
-    void HandleScriptReloadFinished(StringHash eventType, VariantMap& eventData);
-    void HandleScriptReloadFailed(StringHash eventType, VariantMap& eventData);
-    int exitCode_;
-};
-
-int Run()
-{
-    try
-    {
-        // Create the execution context and the application object. The application object is needed to subscribe to events
-        // which allow to live-reload the script.
-        SharedPtr<Context> context(new Context());
-        SharedPtr<Application> application(new Application(context));
-        return application->Run();
-    }
-    catch (std::bad_alloc&)
-    {
-        ErrorDialog("Urho3D", "An out-of-memory error occurred. The application will now exit.");
-        return EXIT_FAILURE;
-    }
-}
+DEFINE_APPLICATION_MAIN(Urho);
 
-Application::Application(Context* context) :
-    Object(context),
+Urho::Urho(Context* context) :
+    Application(context),
     exitCode_(EXIT_SUCCESS)
 {
 }
 
-int Application::Run()
+int Urho::Setup()
 {
     // Check for script file name
     const Vector<String>& arguments = GetArguments();
@@ -89,18 +55,18 @@ int Application::Run()
     {
         if (arguments[i][0] != '-')
         {
-            scriptFileName = GetInternalPath(arguments[i]);
+            scriptFileName_ = GetInternalPath(arguments[i]);
             break;
         }
     }
     
     #if defined(ANDROID) || defined(IOS)
     // Can not pass script name on mobile devices, so choose a hardcoded default
-    scriptFileName = "Scripts/NinjaSnowWar.as";
+    scriptFileName_ = "Scripts/NinjaSnowWar.as";
     #endif
     
     // Show usage if not found
-    if (scriptFileName.Empty())
+    if (scriptFileName_.Empty())
     {
         ErrorDialog("Urho3D", "Usage: Urho3D <scriptfile> [options]\n\n"
             "The script file should implement the function void Start() for initializing the "
@@ -130,98 +96,109 @@ int Application::Run()
             "-sm2        Force SM2.0 rendering\n"
             #endif
         );
-        return EXIT_FAILURE;
+        exitCode_ = EXIT_FAILURE;
     }
     
-    SharedPtr<Engine> engine(new Engine(context_));
-    if (engine->Initialize(Engine::ParseParameters(arguments)))
-    {
+    return exitCode_;
+}
+
+int Urho::Start()
+{
 #ifdef ENABLE_LUA
-        String extension = GetExtension(scriptFileName).ToLower();
-        if (extension != ".lua")
-        {
+    String extension = GetExtension(scriptFileName_).ToLower();
+    if (extension != ".lua")
+    {
 #endif
-            // Instantiate and register the AngelScript subsystem
-            context_->RegisterSubsystem(new Script(context_));
+        // Instantiate and register the AngelScript subsystem
+        context_->RegisterSubsystem(new Script(context_));
 
-            // Hold a shared pointer to the script file to make sure it is not unloaded during runtime
-            SharedPtr<ScriptFile> scriptFile(context_->GetSubsystem<ResourceCache>()->GetResource<ScriptFile>(scriptFileName));
+        // Hold a shared pointer to the script file to make sure it is not unloaded during runtime
+        scriptFile_ = context_->GetSubsystem<ResourceCache>()->GetResource<ScriptFile>(scriptFileName_);
 
-            // If script loading is successful, execute engine loop
-            if (scriptFile && scriptFile->Execute("void Start()"))
-            {
-                // Subscribe to script's reload event to allow live-reload of the application
-                SubscribeToEvent(scriptFile, E_RELOADSTARTED, HANDLER(Application, HandleScriptReloadStarted));
-                SubscribeToEvent(scriptFile, E_RELOADFINISHED, HANDLER(Application, HandleScriptReloadFinished));
-                SubscribeToEvent(scriptFile, E_RELOADFAILED, HANDLER(Application, HandleScriptReloadFailed));
-
-                while (!engine->IsExiting())
-                    engine->RunFrame();
-
-                if (scriptFile->GetFunction("void Stop()"))
-                    scriptFile->Execute("void Stop()");
-
-                return exitCode_;
-            }
-#ifdef ENABLE_LUA
-        }
-        else
+        // If script loading is successful, proceed to main loop
+        if (scriptFile_ && scriptFile_->Execute("void Start()"))
         {
-            // Instantiate and register the Lua script subsystem
-            context_->RegisterSubsystem(new LuaScript(context_));
-            LuaScript* luaScript = GetSubsystem<LuaScript>();
+            // Subscribe to script's reload event to allow live-reload of the application
+            SubscribeToEvent(scriptFile_, E_RELOADSTARTED, HANDLER(Urho, HandleScriptReloadStarted));
+            SubscribeToEvent(scriptFile_, E_RELOADFINISHED, HANDLER(Urho, HandleScriptReloadFinished));
+            SubscribeToEvent(scriptFile_, E_RELOADFAILED, HANDLER(Urho, HandleScriptReloadFailed));
 
-            if (luaScript->ExecuteFile(scriptFileName.CString()))
-            {
-                luaScript->ExecuteFunction("Start");
-
-                while (!engine->IsExiting())
-                    engine->RunFrame();
-
-                luaScript->ExecuteFunction("Stop");
+            return exitCode_;
+        }
+#ifdef ENABLE_LUA
+    }
+    else
+    {
+        // Instantiate and register the Lua script subsystem
+        context_->RegisterSubsystem(new LuaScript(context_));
+        LuaScript* luaScript = GetSubsystem<LuaScript>();
 
-                return exitCode_;
-            }
+        // If script loading is successful, proceed to main loop
+        if (luaScript->ExecuteFile(scriptFileName_.CString()))
+        {
+            luaScript->ExecuteFunction("Start");
+            return exitCode_;
         }
-#endif
     }
+#endif
 
+    // The script was not successfully loaded. Show the last error message and do not run the main loop
     ErrorExit();
     return exitCode_;
 }
 
-void Application::ErrorExit()
+int Urho::Stop()
+{
+    if (scriptFile_)
+    {
+        // Execute the optional stop function
+        if (scriptFile_->GetFunction("void Stop()"))
+            scriptFile_->Execute("void Stop()");
+    }
+#ifdef ENABLE_LUA
+    else
+    {
+        LuaScript* luaScript = GetSubsystem<LuaScript>();
+        if (luaScript)
+            luaScript->ExecuteFunction("Stop");
+    }
+#endif
+
+    return exitCode_;
+}
+
+void Urho::ErrorExit()
 {
-    Engine* engine = context_->GetSubsystem<Engine>();
-    engine->Exit(); // Close the rendering window
+    engine_->Exit(); // Close the rendering window
 
     // Only for WIN32, otherwise the last message would be double posted on Mac OS X and Linux platforms
     #ifdef WIN32
-    Log* log = context_->GetSubsystem<Log>();   // May be null if ENABLE_LOGGING is not defined during built
+    Log* log = context_->GetSubsystem<Log>();   // May be null if ENABLE_LOGGING is not defined during build
     ErrorDialog("Urho3D", log ? log->GetLastMessage() : "Application has been terminated due to unexpected error.");
     #endif
 
     exitCode_ = EXIT_FAILURE;
 }
 
-void Application::HandleScriptReloadStarted(StringHash eventType, VariantMap& eventData)
+void Urho::HandleScriptReloadStarted(StringHash eventType, VariantMap& eventData)
 {
-    ScriptFile* scriptFile = static_cast<ScriptFile*>(GetEventSender());
-    if (scriptFile->GetFunction("void Stop()"))
-        scriptFile->Execute("void Stop()");
+    if (scriptFile_->GetFunction("void Stop()"))
+        scriptFile_->Execute("void Stop()");
 }
 
-void Application::HandleScriptReloadFinished(StringHash eventType, VariantMap& eventData)
+void Urho::HandleScriptReloadFinished(StringHash eventType, VariantMap& eventData)
 {
-    ScriptFile* scriptFile = static_cast<ScriptFile*>(GetEventSender());
     // Restart the script application after reload
-    if (!scriptFile->Execute("void Start()"))
+    if (!scriptFile_->Execute("void Start()"))
+    {
+        scriptFile_.Reset();
         ErrorExit();
+    }
 }
 
-void Application::HandleScriptReloadFailed(StringHash eventType, VariantMap& eventData)
+void Urho::HandleScriptReloadFailed(StringHash eventType, VariantMap& eventData)
 {
+    scriptFile_.Reset();
     ErrorExit();
 }
 
-DEFINE_MAIN(Run());

+ 66 - 0
Urho3D/Urho3D.h

@@ -0,0 +1,66 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// 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 "Application.h"
+
+using namespace Urho3D;
+
+namespace Urho3D
+{
+    class ScriptFile;
+}
+
+/// Urho3D script shell application, which runs a script specified on the command line.
+class Urho : public Application
+{
+    OBJECT(Urho);
+    
+public:
+    /// Construct.
+    Urho(Context* context);
+    
+    /// Setup before engine initialization. Verify that a script file has been specified.
+    virtual int Setup();
+    /// Startup after engine initialization. Load the script and execute its start function.
+    virtual int Start();
+    /// Cleanup after the main loop. Run the script's stop function if it exists.
+    virtual int Stop();
+    
+private:
+    /// Show last error message and set the exit code for error exit.
+    void ErrorExit();
+    /// Handle reload start of the script file.
+    void HandleScriptReloadStarted(StringHash eventType, VariantMap& eventData);
+    /// Handle reload success of the script file.
+    void HandleScriptReloadFinished(StringHash eventType, VariantMap& eventData);
+    /// Handle reload failure of the script file.
+    void HandleScriptReloadFailed(StringHash eventType, VariantMap& eventData);
+    
+    /// Script file name.
+    String scriptFileName_;
+    /// Script file.
+    SharedPtr<ScriptFile> scriptFile_;
+    /// Application exit code.
+    int exitCode_;
+};