Jump to page: 1 2
Thread overview
Style/Structuring question: One vs. multiple global objects
Jul 01, 2007
Henning Hasemann
Jul 01, 2007
Deewiant
Jul 01, 2007
Henning Hasemann
Jul 01, 2007
Kirk McDonald
Jul 01, 2007
Derek Parnell
Jul 02, 2007
Deewiant
Jul 02, 2007
Henning Hasemann
Jul 02, 2007
Deewiant
Jul 02, 2007
Henning Hasemann
Jul 03, 2007
Deewiant
Jul 03, 2007
Henning Hasemann
Jul 03, 2007
Deewiant
Jul 02, 2007
torhu
Jul 02, 2007
Henning Hasemann
Jul 02, 2007
BCS
Jul 03, 2007
Lutger
Jul 03, 2007
Deewiant
July 01, 2007
Say I have a game engine in which nearly all parts including the
engines user need to access some global values.
Example for such would be the screen width & height, the current main
character, the currently displayed scene, the jukebox-object which does
sound stuff, etc...

I used to have one single global game object which stores all the other things mentioned above because deep in my mind there's a "global variables are evil" burned in.

Now where the stuff is getting more complex, this often leads to some
"forward reference" issues because the game module needs to import a
lot (scene module, character module, etc...) but also lots of modules
need game.
So this leads to circular imports which somehow leads to that forward
reference stuff.

Would it be a better idea to have multiple global objects for the different things? I.e. a "currentScene" global in the scene module, a "mainCharacter" global in the character module etc...?

What would you do?

Henning


PS:
I even wrote a small python script which tries to find cycles in the
"import graph" which demonstrated me that not every circular import
leads to a forward reference and that I have much more circular imports
that I'd expected.

-- 
GPG Public Key: http://keyserver.ganneff.de:11371/pks/lookup?op=get&search=0xDDD6D36D41911851 Fingerprint: 344F 4072 F038 BB9E B35D  E6AB DDD6 D36D 4191 1851
July 01, 2007
Henning Hasemann wrote:
> Say I have a game engine in which nearly all parts including the
> engines user need to access some global values.
> Example for such would be the screen width & height, the current main
> character, the currently displayed scene, the jukebox-object which does
> sound stuff, etc...
> 
> I used to have one single global game object which stores all the other things mentioned above because deep in my mind there's a "global variables are evil" burned in.
> 
> Now where the stuff is getting more complex, this often leads to some
> "forward reference" issues because the game module needs to import a
> lot (scene module, character module, etc...) but also lots of modules
> need game.
> So this leads to circular imports which somehow leads to that forward
> reference stuff.
> 
> Would it be a better idea to have multiple global objects for the different things? I.e. a "currentScene" global in the scene module, a "mainCharacter" global in the character module etc...?
> 
> What would you do?
> 

I'd start with making sure "lots of modules need game" == false, to the point that no modules need game. IMHO if you have a kind of "god object" (or module, I don't write objects for such things), nobody should know about its existence. Just like real-life religion. ;-)

If you're really that paranoid about globals (I see no reason to be), try to make something which contains only the external interfaces to your object and put it in a separate module.

After that, try to cleverly eliminate all circular imports in the rest of the program, which probably means creating new modules (i.e. instead of A <-> B, A -> C and B -> C) in addition to just moving stuff around. In general, I prefer creating modules, because it makes the code clear: the alternative is being forced to put something in a module where it clearly doesn't belong.

A possible problem with the above is that you may end up with several very short modules which only contain a few globals. When it looks like this is happening, and I can't find a justifiable way of merging them, I tend to just create a "globals.d".

Overall it's not that simple a problem. I find that allowing circular imports is often an optimization: you can circumvent certain limitations without them, but it's easier to just import.

For instance, in a game, you have a Tile object and an Entity object. A Tile
needs to know of the Entities standing on it and the Entity needs to know where
it's standing. There are, as far as I can think of, three ways to deal with this:

- Put Tile and Entity in the same module, which leads to a "tile_and_entity.d"
with potentially thousands of lines of code. Not very nice.
	- Succumb to the inevitable and use a circular import, which may lead to
forward reference issues and makes the built-in code coverage analyser unusable.
	- Store Entity in Tile, and store x- and y-coordinates in Entity. They can then
be used by modules higher up in the hierarchy to pull the Tile out from an
array, or Map object, or whatever.

When I mention optimization, I'm referring to how the former two solutions allow to store Entity and Tile normally in each other, and how the last solution adds one or more layers of indirection. It's a bit ugly, and it slows things down a bit, but it works.

Another option might be "partial class implementation" as mentioned by davidl recently on the digitalmars.D newsgroup. Object, for instance, is declared in object.d but implemented in internal\object.d. (Here object.d should really be .di since it's only an interface.) Unfortunately, I have no idea how to get this to work: I always seem to get linker errors.

And we can't do what C++ does with its header files (a handy way of avoiding
circular imports):

struct S { int f(); }

int S.f() { return 1; } // like C++'s S::f, but not in D

In my experience, D's abandoning of header files might not have been such a good idea, since especially in games, as you have noticed, there tends to be a lot of coupling between seemingly unrelated things, which makes it very hard to program clean code in D without circular imports. Or then I've just missed something which does make it easy.

Hopefully this helps.

-- 
Remove ".doesnotlike.spam" from the mail address.
July 01, 2007
Deewiant <deewiant.doesnotlike.spam@gmail.com> schrieb (Sun, 01 Jul
2007 19:09:12 +0300):
> I'd start with making sure "lots of modules need game" == false, to the point that no modules need game. IMHO if you have a kind of "god object" (or module, I don't write objects for such things), nobody should know about its existence. Just like real-life religion. ;-)

Interesting strategy. But that would mean things like the main character had to  be accessible in other ways, or?

> If you're really that paranoid about globals (I see no reason to be), try to make something which contains only the external interfaces to your object and put it in a separate module.

I think the paranoia about globals comes from some experiences with or hints for other programming languages such as python etc... where globals are generally considered bad style, though I must confess I never understood why there is/was such a radical talking against them.

Thats one of the main reasons for my post: I'm always a little unsure how to deal with globals stylistically.

What exactly do you mean with external interface?
The point is: the "game" class would need to know about scenes,
characters and so on. So it had to import the proper modules.
And, for example lots of modules deal somehow with the current scene or
the current main character so they had to import game...
Maybe you could give a simple code example of your strategy?

> After that, try to cleverly eliminate all circular imports in the rest of the program, which probably means creating new modules (i.e. instead of A <-> B, A -> C and B -> C) in addition to just moving stuff around. In general, I prefer creating modules, because it makes the code clear: the alternative is being forced to put something in a module where it clearly doesn't belong.

Fully agree here, nearly every class has its own module in my code.

> A possible problem with the above is that you may end up with several very short modules which only contain a few globals. When it looks like this is happening, and I can't find a justifiable way of merging them, I tend to just create a "globals.d".

That globals.d then would look like the following, describing the same problem as game.d:

import mylib.scene;
import mylib.viewport;
import mylib.character;
//...

// globals
mylib.scene.Scene currentScene;
mylib.viewport.Viewport viewport;
mylib.character.Character mainCharacter;

My game.d atm is nearly the same with a "class Game { }" around it plus some simple functions.

> For instance, in a game, you have a Tile object and an Entity object.
> A Tile needs to know of the Entities standing on it and the Entity
> needs to know where it's standing. There are, as far as I can think
> of, three ways to deal with this:
> - Put Tile and Entity in the same module, which leads to a
> "tile_and_entity.d" with potentially thousands of lines of code. Not
> very nice.

Ack.

> 	- Succumb to the inevitable and use a circular import, which
> may lead to forward reference issues and makes the built-in code
> coverage analyser unusable.
> 	- Store Entity in Tile, and store x- and y-coordinates in
> Entity. They can then be used by modules higher up in the hierarchy
> to pull the Tile out from an array, or Map object, or whatever.

You mean:

class Tile {
 // ...
 class Entity {
  int x,y;
 }
}

? I guess you mean something different, but atm I just dont get it...

> In my experience, D's abandoning of header files might not have been such a good idea, since especially in games, as you have noticed, there tends to be a lot of coupling between seemingly unrelated things, which makes it very hard to program clean code in D without circular imports. Or then I've just missed something which does make it easy.

Seems so :-(

> Hopefully this helps.

Today at least a little. But I think if I sleep over it and look at it tomorrow again it'll help more ;-)

Thanks so far!
Henning

-- 
GPG Public Key: http://keyserver.ganneff.de:11371/pks/lookup?op=get&search=0xDDD6D36D41911851 Fingerprint: 344F 4072 F038 BB9E B35D  E6AB DDD6 D36D 4191 1851
July 01, 2007
Henning Hasemann wrote:
> I think the paranoia about globals comes from some experiences with or
> hints for other programming languages such as python etc... where
> globals are generally considered bad style, though I must confess I
> never understood why there is/was such a radical talking against them.
> 

Globals are much less annoying in D than in Python. They are annoying in Python because of Python's scoping rules. For the benefit of those who don't know Python, the issue is this:

i = 12
def foo():
    print i # prints '12'
    i = 20
foo()   # call foo
print i # prints 12; foo did not change 'i'

When it assigns 20 to 'i' inside of foo(), it is actually creating a new name local to foo(), which shadows the global name. To assign to the global name instead, you must use the 'global' keyword.

j = 14
def bar():
    global j
    print j # prints '14'
    j = 40
bar()
print j # prints '40'; bar changed 'j'

You might expect this to lead to some confusion (particularly among newbies to Python), and you'd be right. It is because of this potentially confusing situation that globals are generally frowned upon in Python.

In D, on the other hand, variables have to be explicitly declared. If the only variable with a certain name is at global scope, then using that name will obviously only refer to the global variable:

int i;
void foo() {
    i = 12; // changes the global
}

D goes further by making the shadowing of global variables an error, and making ambiguous situations in general errors. I am much less hesitant to use globals in D than I am in Python.

-- 
Kirk McDonald
http://kirkmcdonald.blogspot.com
Pyd: Connecting D and Python
http://pyd.dsource.org
July 01, 2007
On Sun, 01 Jul 2007 13:07:49 -0700, Kirk McDonald wrote:

> I am much less hesitant to use globals in D than I am in Python.

I'm old-school in that my first programming langugage was COBOL. We were taught that globals were to be avoided because it added to the maintenance effort - and I still agree with that.

The issue is that if you change a global symbol (either at coding time or at run time), you have to take extra effort at finding out what is affected by that change. By keeping symbols more locally, it is easier to find the dependancies.

However, sometimes some things need to be global as they are used in many other modules. For example if your application supports a "-verbose" command line switch. The choice is then whether to expose the variable or an interface to the variable. In other words ...

    if (Global_IsVerbose) ...

or

    if (Global_IsVerbose() ) ...

In either case, I'd recommend naming the entity in such a manner that highlights its 'globalness' effect.

-- 
Derek Parnell
Melbourne, Australia
skype: derek.j.parnell
July 02, 2007
Henning Hasemann wrote:
> PS:
> I even wrote a small python script which tries to find cycles in the
> "import graph" which demonstrated me that not every circular import
> leads to a forward reference and that I have much more circular imports
> that I'd expected.
> 

Have you tried this tool?  It creates import graphs.

http://www.shfls.org/w/d/dimple/
July 02, 2007
torhu <fake@address.dude> schrieb (Mon, 02 Jul 2007 04:01:35 +0200):
> Henning Hasemann wrote:
> > PS:
> > I even wrote a small python script which tries to find cycles in the
> > "import graph" which demonstrated me that not every circular import
> > leads to a forward reference and that I have much more circular
> > imports that I'd expected.
> > 
> 
> Have you tried this tool?  It creates import graphs.
> 
> http://www.shfls.org/w/d/dimple/

Thanks for that, nice tool.
But my graph is just a little to complex to see anything with the naked
eye ,-)

Henning

-- 
GPG Public Key: http://keyserver.ganneff.de:11371/pks/lookup?op=get&search=0xDDD6D36D41911851 Fingerprint: 344F 4072 F038 BB9E B35D  E6AB DDD6 D36D 4191 1851
July 02, 2007
Henning Hasemann wrote:
> Deewiant <deewiant.doesnotlike.spam@gmail.com> schrieb (Sun, 01 Jul 2007 19:09:12 +0300):
>> I'd start with making sure "lots of modules need game" == false, to the point that no modules need game. IMHO if you have a kind of "god object" (or module, I don't write objects for such things), nobody should know about its existence. Just like real-life religion. ;-)
> 
> Interesting strategy. But that would mean things like the main character had to  be accessible in other ways, or?

Like:

struct GodObject {
	MainCharacter dude;

	void gameLoop() {
		engine.actOnInput(dude.getInput());
	}
}

You get the idea. MainCharacter would be in its own module so if something needs its definition, it can import that module, but the "dude" variable itself would always be passed from GodObject.

>> If you're really that paranoid about globals (I see no reason to be), try to make something which contains only the external interfaces to your object and put it in a separate module.
<snip>
> What exactly do you mean with external interface? The point is: the "game"
> class would need to know about scenes, characters and so on. So it had to
> import the proper modules. And, for example lots of modules deal somehow with
>  the current scene or the current main character so they had to import
> game... Maybe you could give a simple code example of your strategy?

See above. Pass around the current main character if you have a "struct/class Game", or define it as a global in maincharacter.d if you're willing to use globals.

>> A possible problem with the above is that you may end up with several very short modules which only contain a few globals. When it looks like this is happening, and I can't find a justifiable way of merging them, I tend to just create a "globals.d".
> 
> That globals.d then would look like the following, describing the same problem as game.d:
> 
> import mylib.scene; import mylib.viewport; import mylib.character; //...
> 
> // globals mylib.scene.Scene currentScene; mylib.viewport.Viewport viewport;
>  mylib.character.Character mainCharacter;
> 

This is why I'd put such definitions in the modules their types are defined in, in this case. Generally, it's the kind of types that don't need to import anything else from the project are the ones that'd end up in a globals.d.

>> - Store Entity in Tile, and store x- and y-coordinates in Entity. They can then be used by modules higher up in the hierarchy to pull the Tile out from an array, or Map object, or whatever.
> 
> You mean:
> 
> class Tile {
> // ...
>  class Entity {
>   int x,y;
>  }
> }
> 
> ? I guess you mean something different, but atm I just dont get it...

I mean:

class Tile {
	Entity standingOnThis;
}
Tile[] tiles;

// in other module...

class Entity {
	int x, y;
}

Then, with the above, we can do:

void getTileOfEntity(Entity e) {
	return tiles[e.y * MAP_WIDTH + e.x]; // or whatever
}

The downside is that you need some sort of "utility module" which imports both Tile and Entity and defines getTileOfEntity. That, or put it in the same module as Tile, which isn't exactly logical.

>> Hopefully this helps.
> 
> Today at least a little. But I think if I sleep over it and look at it tomorrow again it'll help more ;-)
> 
> Thanks so far! Henning
> 

Not a problem. If you can think of something, feel free to enlighten me, too. I'd really like a general-purpose solution which doesn't involve all the kinds of hacks we have to resort to now.

-- 
Remove ".doesnotlike.spam" from the mail address.
July 02, 2007
> Like:
> 
> struct GodObject {
> 	MainCharacter dude;
> 
> 	void gameLoop() {
> 		engine.actOnInput(dude.getInput());
> 	}
> }

Oh, I see we talked about the same thing with different words. The situation is/was roughly like this:

import character;

class Game {
  Character mainCharacter;
  void gameLoop() {
    // ...
  }
}

The point is: When something imports game.d, that in turn will import
character which may itself be tempted to import game etc, so here is no
further division possible without either splitting the god
object "Game" into multiple globals or use the "Dependency inversion"
pattern I found yesterday on the page torhu pointed me to
(http://www.shfls.org/w/d/dimple/).

This pattern seems very useful (I even think it woll solve almost all of my module-structuring problems), but I hesitate to build a structure like this:

thing.d:
--------
class Thing {
  // some functionality, that is also needed by character
}

icharacter.d
-------------
interface ICharacter {
  // ...
}

character.d:
------------
import thing;
import icharacter;
class Character : Thing, ICharacter {
  // ...
}

game.d:
-------
import icharacter;
class Game {
  ICharacter mainCharacter;
}


In this case I think I will prefer making mainCharacter a simple global
variable that can be found in character.d or whatever since the
dependency inversion approach (which I love in general) here has a few
disadvantages:
* unneccessary complicated when it comes to express the relation between
  character and thing
* probably misleading to other coders (ICharacter vs. Character)
* there seem to be only few argument relly against a hand full of
  globals

> See above. Pass around the current main character if you have a "struct/class Game", or define it as a global in maincharacter.d if you're willing to use globals.

atm I tend to the latter

> This is why I'd put such definitions in the modules their types are defined in, in this case. Generally, it's the kind of types that don't need to import anything else from the project are the ones that'd end up in a globals.d.

Ah okay then your glabals.d scheme can work ;-)

> 
> Not a problem. If you can think of something, feel free to enlighten me, too. I'd really like a general-purpose solution which doesn't involve all the kinds of hacks we have to resort to now.

Well the dependency-inversion thingy I found
(http://www.objectmentor.com/resources/articles/dip.pdf) was really
enlightning, maybe it helps you too.
The basic idea is to misuse interfaces to simulate some of the benefits
of C++'s header files.

Small example (dont take the methods too serious ;-))

scene.d:
import thing;
class Scene { Thing myThing; }
Scene currentScene;

thing.d:
import viewport;
class Thing { void draw() { /* do sth. for ex. with viewport.width */ }}

viewport.d:
import scene;
class Viewport {
  void eachFrame() {
    currentScene.draw();
  }
}

This is a nice cycle, which can easily be broken by abstracting scene with an interface (which also yields the possibility to handle movie sequences similar to interactive scenes with a common interface):

new scene.d:
import thing;
import iscene;
class Scene : IScene {
  // same stuff as before
}
class Movie : IScene {
}


iscene.d:
// does not need to import thing
interface IScene {
}


Note that this pattern doesnt seem to work if Scene looks like this:

class Scene : IScene {
  Thing getThing(); // everyone uses this method
}

because then IScene imports thing as well as Scene did so the cycle is not broken here (if you iteratively spread this pattern over all your classes including Thing it should work but then you have lots of unneccessary interfaces and your code would become pretty unreadable).

HTH
Henning

-- 
GPG Public Key: http://keyserver.ganneff.de:11371/pks/lookup?op=get&search=0xDDD6D36D41911851 Fingerprint: 344F 4072 F038 BB9E B35D  E6AB DDD6 D36D 4191 1851
July 02, 2007
Reply to Henning,
[...]

If you don't want globals I'd uses static members (a fancy name for globals that are attached to something else ;) of the class that the data is most closely related to. This might not help with cyclical imports but it does cut out having a "god" object (and thus splitting things up and maybe helping the cycle thing).


« First   ‹ Prev
1 2