From 4b0ca89d7f8a01a28c430b734b34a555c0fb5d81 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Wed, 16 Sep 2015 18:05:18 +0200 Subject: [PATCH] Clean up code and add more comments. --- src/gameobj.d | 37 ++++++++++++++++++++++++++++--------- src/gameobjjumping.d | 29 ++++++++++++++++++++++------- src/main.d | 31 +++++++++++++++++++++++++++---- src/texture.d | 4 ++-- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/src/gameobj.d b/src/gameobj.d index 531d274..07f5f6d 100644 --- a/src/gameobj.d +++ b/src/gameobj.d @@ -18,25 +18,44 @@ module gameobj; import derelict.sdl2.sdl; -interface GameObj { - void prepare(); - void destroy(); - void exec(); - bool wants_to_quit() const; -} - -class GameObjBase : GameObj { +//We want to provide an interface for all game scenes. A game scene should +//implement some basic methods, such as prepare() and exec(), so the main loop +//can call them. +//We also want our interface to store a renderer. This forces us to declare +//an abstract class instead of an interface, becaues interfaces can't have +//constructors nor properties. +abstract class GameObj { public: this (SDL_Renderer* parRenderer) { m_renderer = parRenderer; assert(m_renderer); } + //Derived classes must implement those; + //failure to do so results in a compilation error. + //You need to mark them as "abstract" to let the compiler know there is + //no implementation for them. If you don't mark them as abstract and you + //forget to implement them in the derived game object, you will get a + //linker error instead, and potentially the wrong behaviour as you call them + //from a pointer to the base class. + abstract void prepare(); + abstract void dispose(); + abstract void exec(); + abstract bool wants_to_quit() const; + protected: - SDL_Renderer* renderer() { + //Allow game objects inheriting from GameObj to retrieve the renderer they + //stored through GameObj's constructor. final means it can't be overridden + //(so no v-table entry required for this method); @property means you must + //not use () when you call renderer on an object, eg: r=my_game_obj.renderer + @property final SDL_Renderer* renderer() { return m_renderer; } +//Remember: private members can still be accessed from within this module. +//Or, from a different point of view if you come from a c++ background, within +//the gameobj module, everything is friend to everything else. +//One more good reason for putting each class into a different module. private: SDL_Renderer* m_renderer; } diff --git a/src/gameobjjumping.d b/src/gameobjjumping.d index 0513ff4..1ce34be 100644 --- a/src/gameobjjumping.d +++ b/src/gameobjjumping.d @@ -21,8 +21,11 @@ import derelict.sdl2.sdl; import texture; import std.stdio; -class GameObjJumping : GameObjBase { - //Don't disable this(), it's disable automatically when you define a +//This is one possible game scene. It will draw a texture on the screen +//and it will report user wants to quit if you press the close button in the +//window decoration. +class GameObjJumping : GameObj { + //Don't disable this(), it's disabled automatically when you define a //custorm constructor. //@disable this(); @@ -32,28 +35,40 @@ class GameObjJumping : GameObjBase { m_wants_to_quit = false; } + //Remember: class destructors are not guaranteed to be called + //deterministically, so all resource-freeing code should rather go in the + //dispose() method. ~this() { writeln("Destroying GameObj"); + //m_texture's type is a struct, so destruction is implicit at this point } - void prepare() { + override void prepare() { string texture_name = "bcruiser_normal.bpg"; writeln("Loading texture %s", texture_name); m_texture = Texture(texture_name, renderer()); } - void destroy() { + override void dispose() { + writeln("GameObjJumping dispose()"); + //m_texture.dispose() is what you would normally expect to find here, + //but to demonstrate the use of clear() from the caller code, this + //method is intentionally empty. + //You can verify the program's output as you quit by uncommenting the + //following line: + //m_texture.dispose(); } - //This is the function that is called at every frame - void exec() { + //This is the function that is called at every frame. It represents the + //main loop's body. + override void exec() { SDL_RenderClear(renderer()); m_texture.draw(); SDL_RenderPresent(renderer()); m_wants_to_quit = do_events(); } - bool wants_to_quit() const { + override bool wants_to_quit() const { return m_wants_to_quit; } diff --git a/src/main.d b/src/main.d index 7073087..44c06e5 100644 --- a/src/main.d +++ b/src/main.d @@ -21,13 +21,14 @@ import std.string; import std.conv; import derelict.sdl2.sdl; import gameobjjumping; +import std.typecons : scoped; //Only import scoped from std.typecons module -void run_main_loop (ref GameObj parGame) { +void run_main_loop (GameObj parGame) { parGame.prepare(); do { parGame.exec(); } while (!parGame.wants_to_quit()); - parGame.destroy(); + parGame.dispose(); } int main() { @@ -53,9 +54,31 @@ int main() { scope(exit) { writeln("Destroying renderer..."); SDL_DestroyRenderer(renderer); } //This is our game object - GameObj game = new GameObjJumping(renderer); - scope(exit) clear(game); + //Because GameObjJumping implementation purposedly doesn't destroy resources + //in its dispose method (for this example's sake), you must make sure its + //destructor gets called. You have several choices: + // + //1 - call clear() (deprecated and removed in recent D compilers) + // GameObj game = new GameObjJumping(renderer); + // scope(exit) clear(game); + // + //2 - call destroy() + //note that while game.destroy() is semantically equivalent to the + //destroy(game) syntax, you should never write it that way in case game + //provides its own destroy() method. In that case the method will be + //preferred, and you will end up doing something potentially different than + //what you intended to. For the same reason, don't provide a destroy() + //method in your classes. + // GameObj game = new GameObjJumping(renderer); + // scope(exit) destroy(game); + // + //3 - use scope (deprecated) + // scope GameObj game = new GameObjJumping(renderer); + // + //4 - use scoped (no need for new) + // auto game = scoped!GameObjJumping(renderer); + auto game = scoped!GameObjJumping(renderer); run_main_loop(game); return 0; } diff --git a/src/texture.d b/src/texture.d index 6bc6ec2..5bcbaa7 100644 --- a/src/texture.d +++ b/src/texture.d @@ -44,10 +44,10 @@ public: ~this() { writefln("Destroying texture %s (%s)", m_filename, m_texture); - destroy(); + dispose(); } - void destroy() { + void dispose() { if (m_texture) { SDL_DestroyTexture(m_texture); m_texture = null;