This book is a work in progress, comments are welcome to: johno(at)johno(dot)se
Back to index...Trygve Reenskaug says (paraphrased):
"a crowbar that is made of rubber is more flexible than one made of steel, but it doesn't give you much leverage..."
I interpret this to mean that higher-level, more "rigid" solutions give you more leverage, at the expense of flexibility. This applies, here, to the tendency of a user interface created with IMGUI to be be a large number of if-statements with inlined layout information (the rubber crowbar), as compared to more retained gui systems (the steel crowbar).
FIXME: code goes here...
One of Trygves main challenge to my use of IMGUI in Controllers is that there are lots of if-statements, and that that (apparently) is a smell. Trygve challenged me to find a way to express the mapping between user input and system output at a higher level, in the interest of gaining some leverage.
My spontaneous (same day) comments to this; A high level mapping between user input and IEventTarget calls is probably more interesting, due to the asychronous nature of the applications we are creating (games). One major premise of the write abstraction (IEventTarget) is that thou shalt not expect any response, neither in the form of a return value from the method call (they are supposed to be void) or in the form of any expected callback. This means that any given user input need not map directly to any specific output.
I agree that the fact that the Controller is exposed to layout issues is perhaps a bit low level, but I am unsure how to approach this problem, as my previous experiments with more high level layout mechanisms have all had quite negative results (i.e. they were hard to use). These experiments all involved a stack based object that could be passed to the various "widget-methods" instead of supplying explicit x/y positions.
Another approach might perhaps be to go with named "tag" objects that can be edited implicitly (i.e. editor built into the IMGUI, invoked by holding down some special key). This raises the complexity of the system for the programmer-driven cases, but is probably a must for cases that involve artists and / or designers.
I speculate that Trygve leans towards a more data-driven / table-driven approach, though he points out that tables are only a single implementation, and indeed that switching from if-statements to a table doesn't really change the underlying problem. Since I am personally very much into using code for this (i.e. it can evaluate boolean expressions), perhaps a DSL or "scripting" solution is appropriate. Again, one needs to weigh the gains vs the added complexity. I cannot right now anticipate a case where a non-programmer is coding a Controller, and why "script" when you can do it in code? Any commonality which would potentially make you go faster can be factored out anyway.
One important argument for the if-statement / IMGUI approach is that the code reads similar to the way the widgets appear on the screen, and callback-based systems tend to not do that. The use of layout objects doesn't remove this feature, but I really think that the Controller still needs to explicitly, in code, state the various logic related input-to-IEventTarget mappings, which should include, if not layout, at least the boolean expressions that control their "activeness". In any case, Trygve argued that the if-statement-heavy code is hard to read, and this must of course be tested on other programmers who are not familiar with my code.
Trygve argued that the abstraction level must be raised in order to get more leverage out of the system. A big issue is whether more leverage is actually DESIRED by game coders, at the inevitable expense of flexibility. I intuitively feel that the evolution of DirectX from fixed-function to HLSL is indicative of what game coders want to see, i.e. more flexibility, more immediate-mode, more procedural-style.
Looking at the ZaryCaves code, it almost feels like preprocessor macros would be a solution, which in turn gets me thinking about templates. It looks like, for example in the combo-box case of FuncTool, that what varies is in the form of C++ expressions, i.e. you could probably do a lot of stuff with templates. The question is; is that a good idea for readability? I fear a MindArk here...
I still can't get over the feeling that less code, and very explicit code, is a big plus feature of IMGUI. A very real question is if you really get more leverage out of (for example) MFC as compared to IMGUI, and even if you do, is that something that is inherently useful productivity-wise? I.e. you may potentially get some "bling" for the user, but you get pain for the programmer (manual cache update, callbacks, etc). With some time invested in a good IMGUI, you can still get reuse of the "framework" as well as nice looking "widgets" to boot.
Also, I think that not having to deal with inversion of control is a critical feature. You WANT to have centralized flow control, as Casey points out, and no matter how complex it gets, it's all in the same place, on the same call stack. Also, the inherently 2d "widget" stuff needs to play nicely with the 3d "game" stuff, and if one or the other is Retained to any significant degree while the other is Immediate you immediately (pun intended) get into trouble.
Come to think of it, "leverage" is often an argument given in favor of Retained Mode rendering. For example, OGRE (FIXME: link here) is relatively easy to get up and running, but even beginner programmers / students end up running into a wall, due to OGRE controlling EVERYTHING, very soon.
Working on this a bit; it is apparant that in any given (Meta)Controller, you often have similar gui-widget expressions (i.e, if-statements which will set a current tool/controller) which can be simplified somewhat with a private utility method that extracts the commonality.
FIXME: code goes here...
The next obvious step would be to pull the layout code out of the Controllers. One way would be to have static layout objects (which hold x, y, and probably even text and tooltip information). This would basically objectify some of the stuff that is passed explicitly, but again, if the programmer is doing this, there isn't a huge need for any kind of placement or external text editor (i.e. you would simply end up coding the explicit positional information right into the static objects, as well as the text / tooltips).
This is in one way bad, because it gives you more decentralization of the information required for a given widget, but on the plus side it cleans up doInput() considerably, which enhances readability.
Pulled the layout code out of EditController, and this gave me the actual "do something" code very small and slick in doInput().
FIXME: code goes here...
The layout code, including positions and text and tooltips, is now a private enum in EditController.
FIXME: code goes here...
This separates layout from logic, so I guess the code is easier to read. I could have put the actual ITool that doToolChoice() needs in the Layout object as well, making things even slicker in doInput(), but this would be mixing layout with logic. Also, this is really only interesting if it results in a pretty much standard layout format, which could be moved out of the code completely into data files, again assuming that anyone will ever touch it besides the coders (as well as having an editor for the positions and texts).
The bad side of this is a typical Casey argument; if you decided to remove a widget and / or it's corresponding "handler", you can't simply remove / comment out a single contiguous block of code anymore, because you also need to remove the layout object that otherwise might "leak". Again, decentralization = bad.
Tried the same thing with EntityTool, with less good results.
FIXME: code goes here...
This is mostly due to the fact that the position that ITools start putting up widgets at is supposed to be variable. Right now it isn't really, and we could probably go with a common Layout class for both cases. One question is how to handle that; in Java, an enum is ideal (especially since we can persist this elegantly. Without editing, which implies persistence, this whole approach only helps readability), but then we would have to have a common enum for all classes to share, increasing deps between classes, and this is also essentially the global widget id problem that MFC suffers from. We could do it with private static final members in each given Controller / ITool, and could probably use the same persistence scheme as with enums (i.e. persist the static members). This would remove the need for a common / global single enum for all Controllers.
Just looking at FuncTool, it SEEMS like the layout is indeed dynamic, due to the fact that the "combo-boxes" are exploded, but this will have to be checked.
Again, all of this it sort of depends on what you're doing, if you are one programmer or many, if you are doing code-driven dynamic layout or designer driven edited stuff. This really ties into how your gui works, and the reuse of IMGUI's is really debatable. This is mainly because of the fact that if you can specialize your gui, you get a more high level one, and hence more leverage (more steel, less rubber :)). The antipode here would be that if you had a standard gui, then you would get more leverage through reuse, but there would be some flexibility tradeoffs which force more of the work out into the various clients (the Controllers).
I would argue that the reuse thing is mostly a bogus argument, because it is way too speculative and the sweet-spot is really hard to hit. However, if you are maintaining a large number of projects at once that use the same common gui lib, then sure, the leverage would come from commonality, and perhaps you could even build higher level tools on top of the gui which could be shared amongst the various apps, sort of giving you lots of standardization and "high-levelness / leverage" at the same time.
On the other hand, if you are not ACTIVELY AT THE SAME TIME doing multiple projects, I would argue that leverage through specialization (i.e. making a very specific and very high level gui) would win out.
Trygve had some comments on the general "style" of ZaryCaves2, noting that it was very "public" in that each Controller was basically aware of the entire Model, and that changes in Model's interface would break very many things.
While this is true, and the feared breakage can and will happen, I would argue that this is GOOD. It is in my mind a very important feature of keeping the system as bare-bones as possible, and keeping every programmer in line with what the system really IS.
I shall never forget the pain incurred by the Adapter layer shielding the Commander-level AI in Ground Control 2 from the "real" Model (the Game), both in terms of tons of code that basically translated stuff as well as the tendency of the AI to be designed around premises that were false as well as depend upon aspects of the "game world" that simply did not exist in any way shape or form ("but it says so in my interface!!!!"). Sure, the Adapter layer "shielded" the AI from changes in the Model, but the pain of change was simply felt by the owner of Model instead.
It is easy to envision a project where the division of subsystems is isomorphic to the number of programmers on the team, with the added feature of every single programmer seeing the "system" through the filter of his/her own custom interface (i.e. "these are my needs expressed as an interface, please implement for me!"). You hear about this kind of thing all the time, and I just think it's simply ludicrous; these teams need to communicate more and stop "publishing" interfaces internally within the project.
A change in Model might bust lots of stuff, sure, but isn't that the point? It means that the DOMAIN MODEL HAS CHANGED, i.e. the problem that the program as a whole is trying to solve has CHANGED. If you have Adapters all over the place you are essentially trying to mask or hide the REAL domain model.
Of course I will agree with the common sense of having clients depend on as little as possible, and if you can find a case where a Controller or simply some method in a Controller doesn't need the whole Model, then of course you can simply pass the thing (the subset of Model) that is required.
In a way the above is an example of "compiler driven development". I certainly have a positive feeling when I change something in a class and the compiler immediately tells me that I broke something in a client.
In C++ I re-compile all the time, after writing or changing only a little bit of code. It's almost like I forget what I'm doing if I don't have a list of compiler errors to fix. And after I fix one I will 90% of the time just compile again, even if there are errors left. The Java/Eclipse platform is really cool for using this style, because Eclipse is constantly compiling as you type, which means you don't even have to press a button to get the same thing going for you.
That the compiler catches problems this way (as I incrementally with very small steps change the program) tells me that I have very explicitly used language / code constructs to express my design constraints, very much the opposite of brittleness imho. Alas that Java doesn't have const Class& or const methods like C++ does!
I am however acutely aware of the fact that many people DO NOT use the compiler (with their design hats on) and the linker (in C++, with their implementation hats on) in this way, which makes me think that maybe I should explicitly exemplify this style of coding.
FIXME: do that here...
Of course, to be able to do this in C++ absolutely REQUIRES that your codebase compiles quickly. There is a seemingly popular beef towards C++ stating that big C++ programs simply cannot be compiled quickly. While I have indeed suffered greatly under the unbearable weight of such monstrosities, I counter:
It is NOT impossible to have big C++ programs that compile quickly, it is more a case of avoiding the nasty / it-seemed-like-a-good-idea-at-the-time-and-the-custom-syntax-due-to-operator-overloading-certainly-looks-cool tricks that you can accomplish. Indeed, the fact that Java left out most of the potentially leg-blowing-off stuff that C++ has seems to be one of the main reasons that it builds so fast. Also, recent Microsoft stuff like link time code generation is really cool.
With that said, one of the beefs I do have with Java is the fact that it loads code dynamically. This, coupled with the fact that there are no "headers", means I can't alternate as cleanly between design (in which phase I code based on header declarations) and implementation (in which phase I fix linker errors) like I can in C++. Martin Fowler has however written about (FIXME: link here...) the pattern of always using an interface/implementation pair in Java, so maybe you can at least emulate much the same thing.
Then again, Trygve argues that even with a linker you still can't predict what will happen at runtime due to the existence of polymorphism; but he has a solution for that problem... :) (FIXME: link here...)
Again, this book is a work in progress, comments are welcome to: johno(at)johno(dot)se.
Back to index...