Not logged inGosu Forums
Forum back to Help Search Register Login
Up Topic Gosu / Gosu Exchange / Passing around the window variable
- - By oxymoron Date 2011-12-13 06:44
This is more of a Ruby question, but it is relevant to Gosu.

I'm working on a clone of Zelda 2, and I want to reduce the hassle of passing a window variable between methods. Originally, I used a global variable to do this, but recently I modularized my code by splitting it into several files and now I need a way to pass that variable around. I'm a lazy programmer, so I only want to load this variable once per file and then use its reference variable. That is, I set W = (window variable) and then I call W where needed.

My current solution is to have an empty module named CommonValues (in a file Shared.rb) which is written to using Ruby's built-in #const_set, #instance_variable_set, and related methods. If a file needs to access these variables, I put required_relative 'Shared' at the start of the file, then load a variable as needed. I'm not only doing this for the window variable, but for quest and media folders as well.

The problem with this approach is that it creates a lookup up loop with all the require_relative operations. I'm not sure if I can work around this, or if this approach to sharing variables is itself flawed. (Ruby is my first real language and I've been coding for two months now.)

Here's my code.

In StageArea.rb
05> require_relative 'Shared'

08> module StageArea
12>   W = CommonValues::WINDOW
13>   STAGES_FOLDER = File.join CommonValues::QUEST_FOLDER, 'Graphics/Stages'

104> end

In Quest.rb
07> require_relative 'Shared'
08> require_relative 'StageArea'

11> module Quest

13>   QUEST_FOLDER    = File.join '../Quests',  'Original Quest'
14>   CHEATS_FOLDER   = File.join QUEST_FOLDER, 'Cheats'
15>   DATA_FOLDER     = File.join QUEST_FOLDER, 'Data'
16>   FONTS_FOLDER    = File.join QUEST_FOLDER, 'Fonts'
17>   GRAPHICS_FOLDER = File.join QUEST_FOLDER, 'Graphics'
18>   MUSIC_FOLDER    = File.join QUEST_FOLDER, 'Music'
19>   SCENES_FOLDER   = File.join QUEST_FOLDER, 'Scenes'
20>   SCRIPTS_FOLDER  = File.join QUEST_FOLDER, 'Scripts'
21>   SOUNDS_FOLDER   = File.join QUEST_FOLDER, 'Sound Effects'
22>   VOICE_FOLDER    = File.join QUEST_FOLDER, 'Voice Effects'

24>   CommonValues.const_set(:QUEST_FOLDER, QUEST_FOLDER)

48> end

In z2e.rb
15> require 'rubygems'
16> require 'gosu'

20> require_relative 'Shared'
21> require_relative 'Quest'

28> class Main < Gosu::Window
29>   def initialize
32>     CommonValues.const_set(:WINDOW, self)
33>     CommonValues.const_set(:FRAME_RATE, (1000/self.update_interval).round.to_f)

41>   end
79> end

The error message I get is:
User@COMPUTER ~/Game Engine
$ z2e.rb
~/Game Engine/StageArea.rb:12:in <module:StageArea>': uninitialized constant CommonValues::WINDOW (NameError)
        from ~/Game Engine/StageArea.rb:8:in
<top (required)>'
        from ~/Game Engine/Quest.rb:8:in require_relative'
        from ~/Game Engine/Quest.rb:8:in
<top (required)>'
        from ./z2e.rb:21:in require_relative'
        from ./z2e.rb:21:in

So Main requires Quest, which requires StageArea, which tries to call two variables from CommonValues that are set in Main. Since Main sets these variables before they are ever called, the error must relate to the peculiarities of require_relative. To make sure of this, I placed a binding.pry (pry is an alternative interactive ruby interpreter to irb) on line 11 of StageArea.rb and injected CommonValues.const_set(:WINDOW, "window"). I then successfully called CommonValues::WINDOW and received "window". So it seems the parsing of W = CommonValues::WINDOW happens before CommonValues.const_set(:WINDOW, self) in Main.rb is actually run.

I know Gosu will be phasing out the window variable, but I'll get the same problem from the other variables as well. Is this a good way to tackle the problem, or is there a better solution? I appreciate any alternative ideas :) .
Parent - - By lol_o2 Date 2011-12-13 13:43
Why can't you use a global variable?
Parent - By oxymoron Date 2011-12-17 13:06
I made a bad assumption. When I first split my code into several files, I was getting uninitialized variable errors for $WINDOW. So I looked up the exact scope of globals and read that they can be called anywhere within the program. Well, I assumed "program" meant program file (i.e. only in main.rb) and so tried to implement this other method. Plus, I always hear how bad globals are, so I thought I better just forget about them. But after hearing some more thoughts on it, a global window variable doesn't sound too bad. As for the other folder variables, I changed my approach and just hard-coded them into Shared.rb for now, instead of spreading them over several files which would have become confusing later anyhow.
Parent - - By jlnr (dev) Date 2011-12-13 18:30
You can take a look at require 'gosu/preview', which is a preview of how the interface will look soon- many methods are now on the module level, such as draw_quad, and constructors don't need window arguments anymore.
Parent - - By erisdiscord Date 2011-12-14 01:55
Ooh, when did that happen? I missed the news somehow. :D
Parent - - By jlnr (dev) Date 2011-12-14 09:04
I'm still not sure about color constants and wanted to make it ONE BIG STEP. 0xff0000 being solid red and all, 0xff_ff0000 being the invisibly transparent one. Backwards compatibility for that is a big, fun puzzle. One approach would be to make things constructed with the window argument flip all colors internally. %)
Parent - - By Spooner Date 2011-12-14 21:03
ARGB is not as standard an ordering as RGB/RGBA, but it is a hell of a lot better than TRGB. Although TRGB allows stuff like 0xff0000 for red, it really isn't that much better, I don't think, and it is just as likely to confuse people, just in a different way. Ruby people can be using the Color.rgb, Color.rgba, and Color.argb to do it however they want, so I don't think we matter in the argument :D.
Parent - By jlnr (dev) Date 2011-12-14 21:36
TRGB, if anything, is supposed to be the exception. My design would be based on the assumption that 90% of color constants have full alpha.

But yeah, and to make it even better, Ruby has 0xff_ff000 for red. Even when I still used monospaced fonts, I often got lost in eight hex digits. But C++11 makes it possible to have "ff0000"_rgb now, so maybe I'll just go with that and deprecate the implicit conversion altogether.
Parent - - By RavensKrag Date 2011-12-17 00:39
Eventually, changes will have to break backwards compatibility in the name of progress, no? Best to do it now, before too many people get really invested in Gosu.  It feels like more and more people use this engine all the time ^__^
Parent - - By jlnr (dev) Date 2011-12-17 02:11
But silently breaking all current games would probably scare all current users, me included, off forever. :( This would be hell to debug.
Parent - - By RavensKrag Date 2011-12-17 02:25
But with bundler etc, is this really a problem? They could always just install the older version of Gosu and use that.

Though I will admit that subsequent builds of Gosu which break my game makes me hesitant to upgrade, so it's not a simple thing.
Parent - By jlnr (dev) Date 2011-12-17 03:05
But how would I upgrade my larger projects when the time comes? I could only go over every occurence of 0x... and hope to catch everything. If I forgot one place, I wouldn't notice unless I paid lots of attention while test playing. :(
Parent - By oxymoron Date 2011-12-17 12:55
Thanks. I saw that file before but didn't look into using it. But I think I will now. :)
Parent - - By Spooner Date 2011-12-14 21:14
You do seem to be overengineering. Most people (e.g. Chingu and I've seen it other places too) use "$window = self" in the initializer to have global access to the window, although as jlnr says, this is now unnecessary. They could equally have used "@@instance = self" and made an accessor on the class to access it ("Main.instance"). Use of const_set is really a bad smell - it is critical in certain meta-programming, but that doesn't mean it should generally be used for this sort of thing. A bit like global variables - you should never use them unless the alternative is impossible or incredibly messy ("Never use globals" is rather too dogmatic!).
Parent - By oxymoron Date 2011-12-17 13:13
Thanks, that sounds reasonable. I keep telling myself, "Make something that works first instead of making something perfect," because I know that I'll never get anything accomplished otherwise. And what I know now will probably seem stupid to me later. Those folder variables are really only defined once, so it doesn't make sense for them to be created all over the place. I put them all in Shared.rb for now and later will be retrieving them from a external file.
Up Topic Gosu / Gosu Exchange / Passing around the window variable

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill