Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Where should the RNG live?

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Where should the RNG live?
Karadoc ~~
Member #2,749
September 2002
avatar

From and OO point of view, I really do think that passing references to the RNG is probably the right way to do it. But it's just such a pain in the arse, and I wonder if it's really worth it.

In my current project, the natural place for the RNG to live would be the Game class. Game only uses random numbers in a couple of places though. Most of the random numbers will be used by one of its members, the World class. There is only one World, so passing a reference to that is no problem at all; but after that it starts to get ugly. The World will had a bunch of Zones, each of which will need access to the RNG, and each zone will want to use a random map generator. And of course, a bunch of objects, features, characters and so on. All of these things might all want access to the rng - and I just think it's really ugly to have to pass an extra argument to every damn thing that ever gets created.

And this is why I made the thread. I feel that in this case, the OO design is going to make my code harder to write and harder to understand - and I'm just not sure it is worth it.

I'm reminded of when I did a C programming subject at university, we were meant to write a game (wumpus world) and they told us that we shouldn't use any global variables. I decided stuff that. That would mean that every single function is going to have an extra argument, and it will always be the same argument. It's just an inefficient way to code. I'm going to make a single global struct called "thegame".
Anyway, that's what I did, and I explained why... but I got marked down for it. :( I still think it was the right thing to do for that particular project.

-----------

bamccaig
Member #7,536
July 2006
avatar

Imagine Allegro 4 without globals :

It would be awesome. As it is, it's impossible to really write a good framework around all the global state that is Allegro 4. >:( I tried with Intern's Quest and was inevitably forced to make really ridiculous hacks to make it work. Allegro 5 is much better, but still not perfect in that respect. I've begun a more generalized framework around it that has so far been much nicer, but still not perfect.

screen -> GetScreen()
mouse_pos >> 16 -> GetMousePos() >> 16
palette -> GetPalette()
mouse_b & 1 -> GetMouseB() & 1
key[KEY_ESC] -> GetKeyArray()[KEY_ESC]

That is still global state (either that, or brand new state). ::) Do you know how I know? You aren't passing anything in! Therefore, the state has to either be new, or it has to be global.

Instead, it would be more like:

ALLEGRO_STATE A;

allegro_init(&A);
install_keyboard(&A);
set_color_depth(&A, 24);
set_gfx_mode(&A, GFX_AUTODETECT_WINDOWED, 1024, 768, 0, 0);

...

blit(&A, bitmap, 0, 0, x, y, bitmap_width(bitmap), bitmap_height(bitmap));
int x = mouse_x(&A);
int y = mouse_y(&A);
if(mouse_b_left(&A)) { ... }
if(key(&A, KEY_ESC)) { ... }

Instead of nice, easy to work with globals, you now have a bunch of wasteful accessor functions returning copies of data for no good reason.

No good reason?

screen = 0x4f24; // Have fun, you poor bastard.

Allegro was designed with the global state in mind. I would say that a lot of the design was flawed (I imagine a lot of the developers would agree because they did a pretty drastically different thing with Allegro 5). For starters, the user shouldn't have to do bitwise operations to access data if they're calling an accessor function anyway. They could just call an accessor to do it for them. Secondly, a function call isn't very expensive. If you're doing it a few thousand or million times per frame then perhaps it will matter, but often you'll find that you'll get a much bigger speedup if you can figure out an algorithm to do whatever you're doing fewer times instead. If you absolutely need low-level access for performance reasons then you should bypass the normal API and use an unsafe API instead (but be sure to comment on exactly what your justifications are and warn against it normally so some n00b doesn't come along and mimic your bad practice).

Besides, global state vs. not-global state has little to do with accessor functions. It could just as easily be:

A->screen
A->mouse_pos >> 16
A->pallette // ???
A->mouse_b & 1
A->key[KEY_ESC]

Accessors solve a different problem (and are also very much good practice i.e., you should be using them unless you have a very good reason not to). Namely, they prevent the n00b from having to understand the specific details of the data structure. How many n00bs have we had to try to teach bitwise operations to just to check the state of the mouse or a key? :P Then they have to try to remember these arbitrary low-level details and duplicate them all over their code base (or, SHOCK, abstract it into an accessor any fucking way).

If you do need direct access for performance reasons then you can ignore the API and go directly after the data, and be sure to flag the code as bad practice done purely for performance in a bottleneck.

Also, singletons have two good things going for them :
1) You can't access a singleton before it has been constructed properly. This makes global constructors work in the correct order.

This is a non-issue without global scope. Therefore it's not "going for" Singletons at all.

2) Some classes shouldn't be instantiated more than once, due to heavy initialization or use of system devices.

If a class shouldn't be instantiated more than once then you should only instantiate it once. Moreso, a class shouldn't artificially be limited to being instantiated only once. For example, trying to use a system device that is already in use should fail. If the system lets it happen then there's no reason to say that it can't be done. Either the system, your class, or your application has a design flaw.

Really, get over your hate for globals. Sometimes they're appropriate.

Globals are only appropriate because of design flaws in existing systems and technologies. For example, there is a lot of global state in most popular languages already: C, C++, Java, Perl, Python, Ruby, etc. They all have global state in their standard libraries, which makes others want to add global state to their own applications. Usually you can design around it though and wrap global state in something not global. Global state is never a good thing. If you think it is then you're naive. It's sometimes a compromise you make, but it should never be one that you're happy making.

In my current project, the natural place for the RNG to live would be the Game class. Game only uses random numbers in a couple of places though. Most of the random numbers will be used by one of its members, the World class. There is only one World, so passing a reference to that is no problem at all; but after that it starts to get ugly. The World will had a bunch of Zones, each of which will need access to the RNG, and each zone will want to use a random map generator. And of course, a bunch of objects, features, characters and so on. All of these things might all want access to the rng - and I just think it's really ugly to have to pass an extra argument to every damn thing that ever gets created.

You could always question whether or not these things actually need access to the RNG or whether you're trying to use the RNG in the wrong place. :) If it really is correct for the RNG to be accessed from within that part of the code then to say there's something wrong with it is to say there's something wrong with reality. :P It's never ugly passing dependencies. If you must, create a factory to hold a lot of state and use that to instantiate your objects.

EpicObjectFactory factory(rng, other1, other2, other3);

World world = factory.createWorld(arg1, arg2, arg3);
Zone zone = factory.createZone(arg1, arg2, arg3);

// etc...

If it's the typing you're afraid of then you can often work around that by modularizing the instantiation. If it's the long lines you're afraid of then welcome to the club. :P I always wrap my lines at 74-75 characters (65 in E-mail). You can also just use a competent editor to save you a lot of redundant typing. :P

And this is why I made the thread. I feel that in this case, the OO design is going to make my code harder to write and harder to understand - and I'm just not sure it is worth it.

Global state will make it harder to understand. With global state you have no idea where data and functionality comes from. It just magically got there. That is hard to understand. Code is easiest to debug when you can look at it in small pieces. With global state you can't do that anymore. The change can come from anywhere. You need a debugger to watch an expression and keep track of where it changes. Add concurrency to the mix and hold on tight. ;D

I'm reminded of when I did a C programming subject at university, we were meant to write a game (wumpus world) and they told us that we shouldn't use any global variables. I decided stuff that. That would mean that every single function is going to have an extra argument, and it will always be the same argument. It's just an inefficient way to code. I'm going to make a single global struct called "thegame".
Anyway, that's what I did, and I explained why... but I got marked down for it. :( I still think it was the right thing to do for that particular project.

Then, you sir, are naive. :) Easy now is not always easy in the long run. It's not too bad for one person to keep track of a little bit of global state in a small project. As it begins to build or more people are brought on though it quickly becomes way too much to manage. Dependency injection forces you to think about why a particular module of the application needs some other module. Often you discover that the module actually had no fucking business knowing about the other module at all. You never would have thought twice using global state though.

Append:

Also, if the compiler is doing its job properly, it should hopefully be able to inline/optimize most simple accessors.

Karadoc ~~
Member #2,749
September 2002
avatar

Quote:

Then, you sir, are naive. :) ...

I think you speak sense in most of your post, but I will still defend my decision about having a global "thegame" struct in Wumpus World! If the game was written in C++, I would have made thegame a class, and had a single instance in main() and it would not have been global. But in C, when every function in the entire program is essentially a method of thegame, I figure I might as well just write the code as if the entire thing is in thegame class; and main() is really just thegame.execute() with a different name. And if someone else was reading the code, I don't see how my way of doing it would be any harder to understand at all. If instead, every function passed a thegame pointer to every other function, people would still have to know what's inside thegame, and they would still know that it can be changed by any other function... Really. I don't see the difference except that having a global struct was a notational convenience. The only advantage I can think of for using the point-passing approach is that it would make it easier to run multiple instances of thegame from the same program - but I don't see how that feature could be useful in that particular project.

-----------

bamccaig
Member #7,536
July 2006
avatar

Quote: said:

Then, you sir, are naive. ...

I think you speak sense in most of your post, but I will still defend my decision about having a global "thegame" struct in Wumpus World! If the game was written in C++, I would have made thegame a class, and had a single instance in main() and it would not have been global. But in C, when every function in the entire program is essentially a method of thegame, I figure I might as well just write the code as if the entire thing is in thegame class; and main() is really just thegame.execute() with a different name. And if someone else was reading the code, I don't see how my way of doing it would be any harder to understand at all. If instead, every function passed a thegame pointer to every other function, people would still have to know what's inside thegame, and they would still know that it can be changed by any other function... Really. I don't see the difference except that having a global struct was a notational convenience. The only advantage I can think of for using the point-passing approach is that it would make it easier to run multiple instances of thegame from the same program - but I don't see how that feature could be useful in that particular project.

While you may be correct in this case, I think the lesson the professor was trying to teach was more or less for real world applications. It's impractical for each student to write a huge project that would actually suffer from global state, but at the same time you need to show that things can be done with little extra fuss without it. You were docked points for breaking the rules unnecessarily. :) That is to say, you didn't have to use globals, and it gained you very little. And if the project had been continued and new requirements were added and new features implemented (something you can never foretell in the real world) it could easily grow into something where the globals would have been a hindrance. :)

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

bamccaig said:

It would be awesome.

You really think adding an extra parameter to each and every function call is awesome? You're sick, dude.

bamccaig said:

A->screen
A->mouse_pos >> 16
A->pallette // ???
A->mouse_b & 1
A->key[KEY_ESC]

That is your idea of an improvement? I will never, ever, ever collaborate with you on any code, ever.

bamccaig said:

As it is, it's impossible to really write a good framework around all the global state that is Allegro 4.

If you think this is impossible, then you really suck at coding. I have an entire library built around Allegro 4, and it works perfectly.

bamccaig said:

No good reason?

screen = 0x4f24; // Have fun, you poor bastard.

If you're dumb enough to do things like that, then your programs deserve to fail, and fail hard.

bamccaig said:

Allegro was designed with the global state in mind. I would say that a lot of the design was flawed (I imagine a lot of the developers would agree because they did a pretty drastically different thing with Allegro 5).

And now we have constructs like al_get_bitmap_width(bmp) where all you needed before was bmp->w. Also, now there is a ton of hidden state, which can bite you pretty hard if it changes in code that you forgot changes it.

bamccaig said:

Edgar said:

1) You can't access a singleton before it has been constructed properly. This makes global constructors work in the correct order.

This is a non-issue without global scope. Therefore it's not "going for" Singletons at all.

If Allegro was a singleton, then you would never have newbies trying to create or load bitmaps before allegro was initialized, because they couldn't anymore. But I suppose if they had to pass a valid ALLEGRO_INSTANCE* to every single function they ever wanted to call then it couldn't fail either. :P

bamccaig said:

If a class shouldn't be instantiated more than once then you should only instantiate it once.

And the only way to enforce that is to use a singleton. Thanks for proving my point. :P

bamccaig said:

For example, there is a lot of global state in most popular languages already: C, C++, Java, Perl, Python, Ruby, etc.

Really? Name one globally accesible variable provided by the C or C++ standard libraries? No, RAND_MAX and INTMAX and the like do not count. But I suppose we should all have to create an instance of struct CSTDLIB or class CXXSTDLIB to conform to your standards, and then call methods that operate on those instances. :P:P:P

bamccaig said:

They all have global state in their standard libraries, which makes others want to add global state to their own applications.

Did you really just say that? What global state are you talking about? And just how exactly does that influence others to use global state as well?

bamccaig said:

Global state is never a good thing. If you think it is then you're naive.

Again, and again, there is nothing wrong with using global state where it is appropriate to do so. In my library, there are 69 variables declared extern, and they are meant to be accesible to other code that needs it. Why should I make an accessor function for each and every one of them? That's just stupid.

Also, who are you calling naive? I'm the one with a functional GUI and extension library, while all you seem to have is a bunch of failed projects hosted on GitHub.

bamccaig said:

Global state will make it harder to understand. With global state you have no idea where data and functionality comes from. It just magically got there.

What's hard to understand about a global variable? It's reference didn't get there magically, it got there through an #include statement. Ever heard of grep before?
grep -r -E -I -n "extern.*global_name" libs\include*.*
Wow, there it is.

bamccaig said:

Karadoc said:

I'm reminded of when I did a C programming subject at university, we were meant to write a game (wumpus world) and they told us that we shouldn't use any global variables. I decided stuff that. That would mean that every single function is going to have an extra argument, and it will always be the same argument. It's just an inefficient way to code. I'm going to make a single global struct called "thegame".
Anyway, that's what I did, and I explained why... but I got marked down for it. :( I still think it was the right thing to do for that particular project.

Then, you sir, are naive. :) Easy now is not always easy in the long run. It's not too bad for one person to keep track of a little bit of global state in a small project. As it begins to build or more people are brought on though it quickly becomes way too much to manage.

He's the one who completed a game, so who are you calling naive? Look in the mirror buddy. He was perfectly justified in using a single global to give access to all the functions that needed to use it.

Also, every time I see you use the 'this' pointer inside a class to access member data or class methods I want to light my monitor on fire and cleanse it from displaying ugly pointless code.

When it comes to globals, you have Obsessive Compulsive Disorder. Get some professional help, and lighten up. ;)

Append

bamccaig said:

That is still global state (either that, or brand new state). ::) Do you know how I know? You aren't passing anything in! Therefore, the state has to either be new, or it has to be global.

No, it is not global, because you can't access it without the accessor function. Do you know what global means? If it wasn't declared as extern, or defined outside of main, it is not global (to code that includes your header). :P And there is nothing wrong with modules that use local globals either. :P

Audric
Member #907
January 2001

Really? Name one globally accesible variable provided by the C or C++ standard libraries?

Off the top of my head, I can quote errno. I also wanted to say stdin/stdout/stderr, but after checking online they seem to be literal constants 0 1 and 2.
About notation A->screen etc; it's the same number of characters as "al_" and similarly avoids many problems of name clash.

Karadoc ~~
Member #2,749
September 2002
avatar

Maybe stdout etc aren't real global variables, but they do have at least some kind of global state mojo happening. Check this out:

int main(void)
{
  freopen("output.txt", "w", stdout);
  if (stdout)
    printf("Hello, world?\n");
    
  fclose(stdout);
    return 0;
}

This code compiles and runs, and puts the text in output.txt.

Also, as was mentioned earlier in this thread, the standard library's rand has a global state (or at least static) state.

-----------

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Audric said:

Off the top of my head, I can quote errno.

Okay, I should have said "name a global in C or C++ that actually matters / causes a problem." And none do. This paranoia about using globals is totally unjustified, and ridiculous. errno is global for a reason, because it is shared among many functions that need to report an error number. What would you have in its place instead? Same goes for allegro_error and allero_errno. Should there be a local error object that you pass the address of into each and every function that you call just so it can store the error and error number there? That's absurd.

Karadoc said:

Also, as was mentioned earlier in this thread, the standard library's rand has a global state (or at least static) state.

And how has that ever interfered with anyone's code? Or how has it made understanding how to use rand() more difficult?

Audric
Member #907
January 2001

They aren't variables. Replace stdout by (FILE *)1 in your code and you'll get identical executable.

OICW
Member #4,069
November 2003
avatar

bamccaig said:

There are specific reasons that globals (and Singletons) are bad.
* Makes testing more difficult. You can't properly control the inputs to a function if they're global.
* Makes the code more obscure:

Foo::getInstance().foo();
Bar::getInstance().bar();
Baz::getInstance().baz();

While I agree with making testing more difficult, the latter point is an abomination and failure to use singleton pattern (that's an example of overusing and abusing it). Singletons are typically employed inside the state pattern for the state objects, or when you really need one and only one instance. For example a logfile.

bamccaig said:

Basically, Singletons and all other forms of global state are used by lazy people that don't want to pass data around.

Passing mindlessly same data around and thus introducing an unneeded parameter into all functions that need those data is a failure at design.

bamccaig said:

screen = 0x4f24; // Have fun, you poor bastard.

Yep, therefore a properly written singleton doesn't expose pointers ;-) Don't forget that while using pointers it's your fault to do stupid things like these.

Anyway, I have to say that most of the time strong adherence to strict OOP produces a lot of overhead code for no apparent reason. So mindless following of Design patterns for dummies isn't the best thing, one have to use a common sense as well.

[My website][CppReference][Pixelate][Allegators worldwide][Who's online]
"Final Fantasy XIV, I feel that anything I could say will be repeating myself, so I'm just gonna express my feelings with a strangled noise from the back of my throat. Graaarghhhh..." - Yahtzee
"Uhm... this is a.cc. Did you honestly think this thread WOULDN'T be derailed and ruined?" - BAF
"You can discuss it, you can dislike it, you can disagree with it, but that's all what you can do with it"

bamccaig
Member #7,536
July 2006
avatar

You really think adding an extra parameter to each and every function call is awesome? You're sick, dude.

The addition of the parameter isn't awesome. The elimination of the global state is awesome. The parameter is neutral. It doesn't hurt any to pass an extra parameter. Global state can and does hurt though.

If you think this is impossible, then you really suck at coding. I have an entire library built around Allegro 4, and it works perfectly.

I said a good framework. A good framework is one that is self-contained and safe to use. Something that you (or a n00b collaborator) can't easily fuck up just by typing the wrong operator. :P

If you're dumb enough to do things like that, then your programs deserve to fail, and fail hard.

It isn't about doing it on purpose (or necessarily at all). These are the kinds of things that happen accidentally. We are human. Humans make mistakes. Mistakes with global state can cost you a LOT more time than mistakes with limited-scope state. It doesn't even have to be you. Maybe you're collaborating with somebody, maybe they're hacking away at 4am, maybe they screw up and the compiler doesn't catch it. The program happens to run fine for them by chance and does everything they expected, so they commit and push. You pull in their changes and suddenly the entire program is crashing randomly. Have fun figuring out where the problem is.

And now we have constructs like al_get_bitmap_width(bmp) where all you needed before was bmp->w.

Sure, you can just go on believing that you smarter than me, and the Allegro developers, and ... the world.

Also, now there is a ton of hidden global state, which can bite you pretty hard if it changes in code that you forgot changes it.

The hidden state is global state. That's the part that I'm still not happy with. Strangely, the way you want to fix it is by adding more. :P

But I suppose if they had to pass a valid ALLEGRO_INSTANCE* to every single function they ever wanted to call then it couldn't fail either. :P

Exactly. That is the point of dependency injection. It should become much more clear that you have a dependency to resolve before you can proceed.

And the only way to enforce that is to use a singleton. Thanks for proving my point. :P

Who said anything about enforcing it? It's really quite simple. Instantiate one and pass it throughout the application. You should know you only ever need one and should never create more than one (it would be the same as only needing one file and opening two; it's just wrong). If you do, however, it should still work as per normal. It might not do what the programmer wanted, but it should still do exactly what the object was intended to do.

Really? Name one globally accesible variable provided by the C or C++ standard libraries? No, RAND_MAX and INTMAX and the like do not count.

std::cout.

But I suppose we should all have to create an instance of struct CSTDLIB or class CXXSTDLIB to conform to your standards, and then call methods that operate on those instances. :P:P:P

Who says you should have to instantiate them? The runtime is instantiating them now. There's no reason it can't continue to instantiate them. The entry point of the program would just change.

#SelectExpand
1int main(std::state & s) 2{ 3 std::string name; 4 5 if(s.argc > 1) 6 { 7 name = s.argv[1]; 8 } 9 else 10 { 11 s.out << "What is your name: "; 12 13 std::getline(s.in, name) 14 } 15 16 s.out << "Hello, " << name << "!" << std::endl; 17 18 return 0; 19}

Suddenly main controls what parts of the application have access to everything. Oh, but I know. Then you'd have to type another parameter. ::) And maybe even think. ::)

Did you really just say that? What global state are you talking about? And just how exactly does that influence others to use global state as well?

I already gave you an example. std::cout. There are many more where that came from in every language. It influences others using global state too because rather than bothering to pass an std::ostream into a function or method, most people will just default to directly accessing std::cout or std::cerr. This makes code inflexible and also gets them used to thinking in terms of globals. When they go to design their own class, they think how are they going to get their X into f? The answer is right there on the line above in std::cout. Make it global! NO. >:(

Also, who are you calling naive? I'm the one with a functional GUI and extension library, while all you seem to have is a bunch of failed projects hosted on GitHub.

I write code for a living. Code that I don't own and can't share. ;) The code on GitHub is the code that I have the time and energy to write in my spare time, which isn't much. Then again, I've seen your code quality, and I have no interest in using any of the code I've seen you write. ::) I'd be surprised if anyone other than you used it.

No, it is not global, because you can't access it without the accessor function. Do you know what global means? If it wasn't declared as extern, or defined outside of main, it is not global (to code that includes your header). :P And there is nothing wrong with modules that use local globals either. :P

Do you know what global means? Apparently not. extern isn't limited to your file. I can declare your variable extern myself and access it. Nothing is going in to those example function calls so anything coming out has to have been created in the function or has to exist globally. There's no way around that.

Audric said:

I also wanted to say stdin/stdout/stderr, but after checking online they seem to be literal constants 0 1 and 2.

stdin/stdout/stderr are still global because those constant numbers refer to file streams. I'm not sure of the details, but IIRC open files are stored in a table and the file descriptor (of which I'm not sure stdin, stdout, stderr directly are) are an index into this table. Regardless of the implementation details, these numbers grant you access to these file streams from anywhere in the application, so they are global. It's no different than knowing a pointer for some random object. If you can know the address from anywhere in the program then you can access the data and it is effectively global.

That makes it pretty much impossible to control a program's or library's access to the standard streams. That might not seem like a problem, but imagine you (not just Audric; I'm speaking to all readers now) have some complex program to write and for whatever reason there's some part of it that you don't want to write (maybe you don't have the expertise, maybe you don't have the time, maybe it's against your religion; it's hypothetical). Meanwhile, you find the one library on the face of the Earth that does exactly what you want! The only caveat: the library is reading from stdin or writing to stdout or stderr directly and the project specifications are very strict about user-interaction and output; and these unauthorized reads and writes violate the spec. :-/ The library is made inflexible by accessing the global state of these standard streams, and in this contrived hypothetical example, it makes it worthless. The programmer is doomed. The end.

It is certainly convenient to have these global streams, but it's certainly not necessary, and the industry has learned the hard way that unnecessary global state is expensive (in terms of programmer time and software liability). That's why it's generally agreed upon now that global state is bad practice and that you should avoid it whenever possible. The standard streams are certainly a good idea, but I think it would be better if the OS abstracted them more and passed them into the entry point (e.g., main) in some way instead of making them global.

...global state (or at least static) state.

Static state is global state. They are the same thing. A static variable is not necessarily a "global variable" (but that's just language semantics), but it is still global state.

Okay, I should have said "name a global in C or C++ that actually matters / causes a problem." And none do.

Actually, errno is a perfect example of global state that causes problems. For starters, it isn't obvious which functions use it and which ones don't. You have to go memorize the manual for every library and module of your program to know. It reports errors so it certainly does matter (albeit, a lot of your code seems to ignore errors outright, so no surprise you think it doesn't matter). Handling errors often means calling more functions that may set errno. It makes robust error handling very difficult. Probably why exception handling was invented to replace it. :P

What would you have in its place instead? Same goes for allegro_error and allero_errno. Should there be a local error object that you pass the address of into each and every function that you call just so it can store the error and error number there? That's absurd.

That's actually not absurd. Many libraries do exactly this. It's a rather powerful and flexible system. Of course, exception handling is effectively the same exact thing, except instead of passing an object in to be filled with error information, the object with error information is created where the error occurs and copied (or "referenced" in one or another sense of the word) to the handler block.

OICW said:

Yep, therefore a properly written singleton doesn't expose pointers ;-) Don't forget that while using pointers it's your fault to do stupid things like these.

If a Singleton has state that can be affected from outside in ways that would affect the way it behaves then it's no different (hint: basically any useful Singleton will, unless it's just returning a constant :P). The fact that the program crashes when you invalidate the screen pointer is irrelevant. It could have been a simple counter or string. Making it invalid will produce incorrect results from the software. If a program isn't doing the one right thing then then it's broken and useless.

james_lohr
Member #1,947
February 2002

I totally agree with the Bam-boy on this one.

Also, who are you calling naive? I'm the one with a functional GUI and extension library, while all you seem to have is a bunch of failed projects hosted on GitHub.

I'm afraid you genuinely are naive. You've got what, a GUI library that only you have coded for and used? Congratulations, you've achieved what exactly? If you're going to needlessly mock other people, expect to be mocked. I wrote my first GUI library before I accepted that indents were useful: my childhood code was the very worst type of abomination imaginable, so functional output has very little bearing on coding quality.

Nowadays the code I write gets peer reviewed, and goes into production where I get called at 3 AM to support it if it fails because the company is losing thousands of $$$ a second (thankfully my production code has never failed :P). I'm not the only one in my team who gets pissed off when we have to support code with singletons or global state. Imagine being woken up at 3 AM and having to investigate some sort of production issue caused by code that contains arbitrary globals and singletons that could have been instantiated in God-knows what manner. It's a fucking nightmare.

You don't have any sort of experience with real collaborative or critical coding, so I don't really think you're qualified to argue for or against them in a professional environment.

Allegro isn't much more than a fun little library to hack around with, and that's the only reason why its use of globals is acceptable. If you want to hack together a little library of your own that most likely noone other than you is going to use, then by all means go wild with globals. Most likely you have no idea of the number of layers of abstraction commonly used in commercial code or the sheer volume of code, so you have no real appreciation for how complicated things get when people stop following best practices.

Audric
Member #907
January 2001

About your case with the library "that does everything you need, except (...)" :
In the end, if you don't have access to the source code, you always lose :/

At work, even in C# which has great language facilities (starting with hierarchical namespaces), I have seen many cases where something is planned for reusal (being re-used?) but no matter what, by the time it is operational, the new callers have subtly different needs.

edit: To James Lohr : I think it's important to not give the same advice to people who are learning or fluent in a language (library etc), and whether the result is an indie game prototype (that has 75% chance not getting to the "mostly finished" phase), or a professional piece of code that is going to "earn its pay" for several years.

Vanneto
Member #8,643
May 2007

I waste so much time reading all these walls of text. It is informative though.

What I usually do to avoid globals is I make a program for each module my application needs. For example, a PRNG would be implemented in a prng.exe program in the bin directory.

Then I use popen to open a pipe to the PRNG program and redirect the output so I can actually use the numbers. Its quite modular, the only downside is my /bin directory is filled with 500+ mini programs... :-/

In capitalist America bank robs you.

Karadoc ~~
Member #2,749
September 2002
avatar

/dev/urandom is a pretty cool resource; but I'd still always build the PRNG into my program, for efficiency and control.

For example, awhile back I wrote a physics simulator which needed a huge amount of random numbers. I used the mersenne twister, as usual, but I used /dev/random for the seed. (Having a good seed was important because I was running 10 - 20 instances of this program at a time. Seeding with time(NULL) would have been a disaster.)

-----------

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

bamccaig said:

I said a good framework. A good framework is one that is self-contained and safe to use. Something that you (or a n00b collaborator) can't easily fuck up just by typing the wrong operator.

You've never even seen my library. How would you know it's not a good framework, self contained, and safe to use? You're making blind judgements here.

bamccaig said:

It isn't about doing it on purpose (or necessarily at all). These are the kinds of things that happen accidentally. We are human. Humans make mistakes. Mistakes with global state can cost you a LOT more time than mistakes with limited-scope state. It doesn't even have to be you. Maybe you're collaborating with somebody, maybe they're hacking away at 4am, maybe they screw up and the compiler doesn't catch it. The program happens to run fine for them by chance and does everything they expected, so they commit and push. You pull in their changes and suddenly the entire program is crashing randomly. Have fun figuring out where the problem is.

You can make the exact same mistakes with passed references to data that you can make with global data. If you're not smart enough to sort out which data belongs to who, then you have no business being a programmer. Most extern variables are semantically read only, and are provided for simple access. If you're dumb enough to modify read only data, then you deserve to have problems.

bamccaig said:

Sure, you can just go on believing that you smarter than me, and the Allegro developers, and ... the world.

I never said I was smarter than everyone. Don't put words in my mouth. The Allegro developers aren't perfect either, and there are many things I prefer about the way Allegro 4 works to the way Allegro 5 works.

bamccaig said:

Exactly. That is the point of dependency injection. It should become much more clear that you have a dependency to resolve before you can proceed.

Recently I've been working on function parsers that take a written function and turn it into an evaluatable function call. For this, there are three sets of functions - user defined functions, predefined unary functions and predefined binary functions. I use three globals for them, a map and two sets. This gives easy access to the function parsers that use them. If I used your precious little dependency injection, I would have to wrap them in an object, make the user instantiate that object and then pass it into every function call that needed it. What a giant fucking convoluted waste of time.

bamccaig said:

Who says you should have to instantiate them? The runtime is instantiating them now. There's no reason it can't continue to instantiate them.

But then they're still global, oh no!

bamccaig said:

Suddenly main controls what parts of the application have access to everything. Oh, but I know. Then you'd have to type another parameter. ::) And maybe even think.

You'd have to type another parameter for everything you call. That's wasteful, inefficient, and just plain dumb.

bamccaig said:

It influences others using global state too because rather than bothering to pass an std::ostream into a function or method, most people will just default to directly accessing std::cout or std::cerr. This makes code inflexible and also gets them used to thinking in terms of globals.

Provide some actual evidence that one global leads to another, and another, and so on, or stop making this claim. I pass an ostream& to all functions that output in my program, so I didn't do this. I guess I just have more willpower to resist the giant temptation that is globals. :P

bamccaig said:

Then again, I've seen your code quality, and I have no interest in using any of the code I've seen you write. ::) I'd be surprised if anyone other than you used it.

And just what is it about my code that makes it of such poor quality? Come on, tell me. And yes, currently I'm the only one using my code, but that's to be expected, since I haven't released it yet! If you look at the forums, you can see many examples of people using my code after I gave it to them as an example, and guess what, it all works!

bamccaig said:

Do you know what global means? Apparently not. extern isn't limited to your file. I can declare your variable extern myself and access it. Nothing is going in to those example function calls so anything coming out has to have been created in the function or has to exist globally. There's no way around that.

Again, you're making contrived examples of how using globals is dangerous. If you declare someone else's symbol as extern and then use it, what do you expect other than problems? It's just fucking dumb. Global to every module using extern is not the same thing as global to a single module, but you're so keen on destroying every single instance of global variables that you can't see that.

bamccaig said:

albeit, a lot of your code seems to ignore errors outright, so no surprise you think it doesn't matter

You're making shit up again. Almost all my code deals with errors where they can occur. Provide an example of code I've provided that ignores errors where it matters or shut up.

bamccaig said:

That's actually not absurd. Many libraries do exactly this. It's a rather powerful and flexible system.

Which libraries do this exactly? And what's so great about adding a parameter to every function you call?

bamccaig said:

Of course, exception handling is effectively the same exact thing, except instead of passing an object in to be filled with error information, the object with error information is created where the error occurs and copied (or "referenced" in one or another sense of the word) to the handler block.

Oh, exceptions are great too. Your program mysteriously terminates with no explanation, and no way to get a backtrace in your debugger. :-/ Now you have to wrap all your code in try catch blocks and memorize every function that could possibly throw an exception. Great. That said, I still use exceptions where appropriate.

I'm afraid you genuinely are naive. You've got what, a GUI library that only you have coded for and used? Congratulations, you've achieved what exactly? If you're going to needlessly mock other people, expect to be mocked. I wrote my first GUI library before I accepted that indents were useful: my childhood code was the very worst type of abomination imaginable, so functional output has very little bearing on coding quality.

Naive about what exactly? That I've got functional code that makes my life 10x easier and he's got... uhm ... nothing completed? And yes I'm the only one who uses it, because I haven't released it yet. ::) It wasn't needless mocking, but rather a reality check. You both call me naive, but when my library is finished and I start using it to make programs I'll be able to create programs much faster and I'll know that they will work correctly because I've tested my library fairly thoroughly. And having a functional program may not depend on quality code, but quality code has a large bearing on productivity. So if his code has such high quality by strictly adhering to a non-sensical no globals policy, then where are all the fruits of his labor then?

James Lohr said:

I'm not the only one in my team who gets pissed off when we have to support code with singletons or global state. Imagine being woken up at 3 AM and having to investigate some sort of production issue caused by code that contains arbitrary globals and singletons that could have been instantiated in God-knows what manner. It's a fucking nightmare.

You're professional developers and you've never heard of grep before? It will give you each and every instance where the symbol in question was used, and then you know exactly who changes it when. If a singleton is giving you trouble, it is because it wasn't designed well. If globals are giving you trouble, then someone is misusing them. Fire whoever wrote the bad singleton / abused a global, and call it a day. ::)

James Lohr said:

Allegro isn't much more than a fun little library to hack around with, and that's the only reason why its use of globals is acceptable. If you want to hack together a little library of your own that most likely noone other than you is going to use, then by all means go wild with globals. Most likely you have no idea of the number of layers of abstraction commonly used in commercial code or the sheer volume of code, so you have no real appreciation for how complicated things get when people stop following best practices.

We have yet to see who will or won't use my library, as I haven't released it yet. Allegro is mainly a low level library, and as such, it can be hard to be productive with it. My library aims to fill this gap, and make using Allegro even easier and faster than before, along with providing a GUI and many extensions. Allegro's use of globals isn't even that bad in my opinion, as things that are meant to be shared like the system driver and the graphics driver are. If a single module needs to share some data amongst itself, why shouldn't it? You're making unnecessary restrictions. Globals, when used properly, can be an effective programming tool. But you're a so called professional, so who am I to argue? ::)

orz
Member #565
August 2000

While we're on the subject of RNGs instead of global variables, I should mention that I've written a OO C++ RNG library, which comes in 2 flavors, one for RNG users, one for RNG researchers (the research one also includes statistical tests and some deliberately flawed RNGs).

It's available at https://sourceforge.net/projects/pracrand/files/

Actually, even on the subject of OO design and/or global state, I'd be interested in any comments on the design of the library. The only global state is internal to the auto-seeding mechanism, though I do advise users to declare a global RNG or global/TLS RNG unless they need multiple RNGs.

Typical usage looks like this:

#include "PractRand_full.h"
#include "PractRand/RNGs/jsf32.h"
PractRand::RNGs::Polymorphic::jsf32 rng(PractRand::SEED_AUTO);
int main(int argc, char **argv) {
  PractRand::initialize_PractRand();
  std::cout << "random uniform int in [0,6): " << rng.randi(0, 6) << std::endl;
  return 0;
}

While the code there calls initialize_PractRand(), that is not actually necessary unless the first usage of SEED_AUTO occurs in re-entrant code.

Various details:
- RNGs selection: wide variety of recommended RNGs with different strengths & features. Documentation that lists all recommended RNGs with their respective strengths in easy to compare format, though much of that is subjective.
- legal issues: Unencumbered, mostly public domain.
- RNG interface: Each recommended RNG is available as either a polymorphic class (for convenience) or a raw implementation (for maximum control & performance).
- Performance: Almost all of the recommended RNGs are fast. Some are very very fast. Most are optimized. Some seed very quickly. I also attempt to keep header size relatively small to maximize users compilation speeds.
- RNG seeding: during construction each full RNG requires either a 64 bit integer as a seed or PractRand::SEED_AUTO (which tells the library to generate a seed automatically) or PractRand::SEED_NONE (which tells the library that you plan to seed this RNG at a later time), or another RNG (which is used to generate a random state for the new RNG). Special entropy-pool classes (which are also RNGs) are available to help users wishing to engage in more complex seeding schemes.
- RNG distributions: all full RNGs offer a built-in minimum set of basic distributions - raw 8/16/32/64 bit words, uniform floating point & integer numbers, and a few variations on that. In addition to the basic distributions they also are compatible with Boost/C++0x distributions.
- RNG exotic usage: support for things like serialization, entropy pooling (not supported on most RNGs), random access (not supported on most RNGs), etc

 1   2 


Go to: