digitalmars.D.learn - Style/Structuring question: One vs. multiple global objects
- Henning Hasemann (28/28) Jul 01 2007 Say I have a game engine in which nearly all parts including the
- Deewiant (52/75) Jul 01 2007 I'd start with making sure "lots of modules need game" == false, to the ...
- Henning Hasemann (47/84) Jul 01 2007 Interesting strategy. But that would mean things like the main
- Kirk McDonald (39/44) Jul 01 2007 Globals are much less annoying in D than in Python. They are annoying in...
- Derek Parnell (21/23) Jul 01 2007 I'm old-school in that my first programming langugage was COBOL. We were
- Deewiant (38/89) Jul 02 2007 Like:
- Henning Hasemann (102/122) Jul 02 2007 Oh, I see we talked about the same thing with different words.
- Deewiant (9/13) Jul 02 2007 It incurs runtime penalties and makes the code too cluttered, IMHO.
- Henning Hasemann (12/27) Jul 02 2007 How big are those penalties? What are the extra steps your machine has
- Deewiant (27/51) Jul 02 2007 Probably tiny enough to be unnoticeable on even modern cellphones. I'm n...
- Henning Hasemann (37/58) Jul 03 2007 Okay, fully agreed. I wouldnt not follow this pattern with *that*
- Deewiant (18/38) Jul 03 2007 No, it doesn't (and that's a good point, that probably wouldn't actually...
- torhu (3/9) Jul 01 2007 Have you tried this tool? It creates import graphs.
- Henning Hasemann (9/20) Jul 02 2007 Thanks for that, nice tool.
- BCS (7/7) Jul 02 2007 Reply to Henning,
- Lutger (16/38) Jul 02 2007 First think about if they really need to be global variables. Does you
- Deewiant (6/10) Jul 02 2007 Not global per se, but they can be module-level variables with the "pack...
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
Jul 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.
Jul 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
Jul 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(): i = 20 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 j = 40 bar() 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
Jul 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
Jul 01 2007
Henning Hasemann wrote:Deewiant <deewiant.doesnotlike.spam gmail.com> schrieb (Sun, 01 Jul 2007 19:09:12 +0300):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.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?<snip>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.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.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.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;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.- 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...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.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
Jul 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 globalsSee 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 latterThis 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
Jul 02 2007
Henning Hasemann wrote: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.It incurs runtime penalties and makes the code too cluttered, IMHO. One thing I came up with was doing the "tile_and_entity.d" like so: module tile_and_entity; mixin(import(tile.d)); mixin(import(entity.d)); Should work, but quite hacky. -- Remove ".doesnotlike.spam" from the mail address.
Jul 02 2007
Deewiant <deewiant.doesnotlike.spam gmail.com> wrote:Henning Hasemann wrote:How big are those penalties? What are the extra steps your machine has to do when you compare acces through an interface with direct access to an object?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.It incurs runtime penalties and makes the code too cluttered, IMHO.One thing I came up with was doing the "tile_and_entity.d" like so: module tile_and_entity; mixin(import(tile.d)); mixin(import(entity.d)); Should work, but quite hacky.Looks like a good idea for such situations with two modules that need each other, but if I understand correctly that wouldnt be able to handle all the things that DIP can, or? 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
Jul 02 2007
Henning Hasemann wrote:Deewiant <deewiant.doesnotlike.spam gmail.com> wrote:Probably tiny enough to be unnoticeable on even modern cellphones. I'm not sure how inheritance works at the asm level. But it's a matter of principle: when all I want is to remove dependencies, why do I need to create a slew of objects? BTW, I just realized that when I mentioned "external interfaces" in my original response I was alluding to the same kind of abstraction talked about in the dependency inversion article, I just had the wrong terminology and forgot about it later. But I still don't advocate the kind of trash mentioned in the article. For instance, there's code with a Button object and a Lamp object. Fine. But then he goes on to create a ButtonClient and ButtonImplementation object? The hell?! This, IMHO, leads to extremely complicated and obtuse interfaces like the Java Platform APIs, where you have String, StringBuffer, StringBuilder, StringHolder, etc. Or ImageInputStream, ImageInputStreamImpl, ImageInputStreamSpi, and the same ImageOutput*, and ImageReader, ImageWriter, ImageReaderSpi, ImageWriterSpi, ImageReaderWriterSpi, ImageIO. So if you have no clue how to read an image from a file there are 6 classes which might be able to do what you want, based on their names. Besides, for an application it makes less sense to do such than for a library. Why make a ButtonImplementation and ButtonClient if you _know_ there's going to be only one type of Button?Henning Hasemann wrote:How big are those penalties? What are the extra steps your machine has to do when you compare acces through an interface with direct access to an object?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.It incurs runtime penalties and makes the code too cluttered, IMHO.If you put your whole program in one module, it can! <g> I'm not sure, but combined with the other techniques it should help things, because that's the only case I've run into so far which can't really be solved well. How would you do this with DIP? -- Remove ".doesnotlike.spam" from the mail address.One thing I came up with was doing the "tile_and_entity.d" like so: module tile_and_entity; mixin(import(tile.d)); mixin(import(entity.d)); Should work, but quite hacky.Looks like a good idea for such situations with two modules that need each other, but if I understand correctly that wouldnt be able to handle all the things that DIP can, or?
Jul 02 2007
Deewiant <deewiant.doesnotlike.spam gmail.com> wrote:But I still don't advocate the kind of trash mentioned in the article. For instance, there's code with a Button object and a Lamp object. Fine. But then he goes on to create a ButtonClient and ButtonImplementation object? The hell?! This, IMHO, leads toOkay, fully agreed. I wouldnt not follow this pattern with *that* enthusiasm. Atm I try to find as few cases as possible where it is needed to break my cycles *and* I try to only create interfaces that have a meaning that is distinguishable from the original class in meaning as well as in naming. For example, before I had: class Thing { } class Character : Thing { } now it will become: interface ISceneEntity { } class Thing : ISceneEntity { } class Character : ISceneEntity { } (which works since there is nearly no functionality in Thing)extremely complicated and obtuse interfaces like the Java Platform APIs, where you have String, StringBuffer, StringBuilder, StringHolder, etc. Or ImageInputStream, ImageInputStreamImpl, ImageInputStreamSpi, and the same ImageOutput*, and ImageReader, ImageWriter, ImageReaderSpi, ImageWriterSpi, ImageReaderWriterSpi, ImageIO. So if you have no clue how to read an image from a file there are 6 classes which might be able to do what you want, based on their names.Ack. Thats something nobody wants. (Except the javaists maybe)Besides, for an application it makes less sense to do such than for a library. Why make a ButtonImplementation and ButtonClient if you _know_ there's going to be only one type of Button?Another ack, but what I'm writing is a library ;-)If you put your whole program in one module, it can! <g>Does putting everything in one module really avoid all forward reference issues?I'm not sure, but combined with the other techniques it should help things, because that's the only case I've run into so far which can't really be solved well. How would you do this with DIP?itile.d: interface ITile { } tile.d: import itile; import ientity; class Tile : ITile { /* use entity via interface here */ } ientity.d: interface IEntity { } entity.d: import ientity; import itile; class Entity : IEntity { /* use tile here via interface */ } Yes, for such a case your pattern seems better at least if you dont find other reasons for creating IEntity and ITile. -- 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
Jul 03 2007
Henning Hasemann wrote:Does putting everything in one module really avoid all forward reference issues?No, it doesn't (and that's a good point, that probably wouldn't actually work). But it solves circular dependencies between modules.itile.d: interface ITile { } tile.d: import itile; import ientity; class Tile : ITile { /* use entity via interface here */ } ientity.d: interface IEntity { } entity.d: import ientity; import itile; class Entity : IEntity { /* use tile here via interface */ } Yes, for such a case your pattern seems better at least if you dont find other reasons for creating IEntity and ITile.That's the whole point. Entity and Tile are already the absolute base classes, they just happen to need to be aware of each other. The ITile/IEntities are just substitutes for C++ header files. Of course, another way: class Entity { Tile t; } class Tile { Object e; // whenever needed, use cast(Entity)e } This reminds me a bit of pre-generics Java, actually, where the contents of Arrays and the like could only be Objects. (Or something like that: I can't remember exactly.) -- Remove ".doesnotlike.spam" from the mail address.
Jul 03 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/
Jul 01 2007
torhu <fake address.dude> schrieb (Mon, 02 Jul 2007 04:01:35 +0200):Henning Hasemann wrote: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 1851PS: 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/
Jul 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).
Jul 02 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...First think about if they really need to be global variables. Does you renderer need to access the sound system? Does your window class need to know the main character and that character need to know the screen width and height? All these things you mention do not need to be global.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.Global variables are not evil, but they can be 1) costly in terms of maintenance and bugs and 2) indicative of a less than clear design. Same goes for cyclic dependencies. But, like goto, it's not a hard rule.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? HenningThat would already be better, but these globals can be eliminated altogether. Some of the things I do: - think about what really needs access to these variables, often a lot of code does not use all things you are tempted to make global - isolate these 'globals' in one place and let other parts use that. For example screen width and height can be used by the window class and then some select few classes depend on the window class. Problem solved. - use abstractions such as interfaces when appropiate
Jul 02 2007
Lutger wrote:First think about if they really need to be global variables. Does you renderer need to access the sound system? Does your window class need to know the main character and that character need to know the screen width and height? All these things you mention do not need to be global.Not global per se, but they can be module-level variables with the "package" privacy specifier. Thus you can have foo.sound.globals which can only be accessed by the sound system. -- Remove ".doesnotlike.spam" from the mail address.
Jul 02 2007