|
Okay i got some IMGUI working in my shiny new MVC architecture, and i'm confused (again). IMGUI is by design both drawing and state management so where does that go? controller.input can't draw and controller.output can't change the model. currently all imgui code lies in output where model is const so the imgui can't affect the model. I have a game state saying MENU or GAME in my model which i want to change if i click a button in the gui which lies in a menucontroller currently selected by the metacontroller as current controller. I guess the question is how does the menucontroller change the model from IMGUI. right now the only way i can see is storing that this is what i want done and then in the next menucontroller.input call i set the new state on the model. Is this how it should be done? i can also do the opposite of course. store and defer rendering of the ui components from input and rendering them on output. Also, is this how you manage IDs? http://sol.gfxile.net/imgui/ch04.html |
Submitted by SolidBenny
Wed, 08/12/2009 - 21:48
|


alternatives
I wouldn't agree that IMGUI is about state management...
I assume that what you basically want to do is at some point:
if(doButton())
{
//change some state in model
}
If you are using IEventTarget then that is what you need to call to change the state in Model. Even if the IEventTarget reference you are using is the Model you still don't conceptually know that / see that from the calling code:
Controller::doInput(IEventTarget& aTarget, const Model& aModel);
This is of course assuming that you don't already have a const & to the Model via View, as in my earlier examples.
The whole split of input() and output() in Controller(s) is basically my (johno's) style, and I personally do IMGUI stuff in Controller::doInput(IEventTarget& anET).
This means that each doButton() call does hit-testing and mouse button checks within that call and returns the results directly. doButton() also stores whatever rects, lines, bitmaps, and text required to render said button at a later time (that is, in doOutput()).
In Controller::doOutput() I do drawing, and then at the end of that method the IMGUI draws all cached rects, lines, bitmaps, texts, etc. This cache lives for "half a frame" so to speak.
Now for the beginner this is obviously more complicated than it needs to be, so let's look at what (I think) hObbE does.
Instead of your main loop being:
controller.input();
model.update();
controller.output();
you can do:
model.update();
controller.update();
In the latter case, you can do all input and output together while rendering your scene. This means that your typical doButton() can do immediate hit-testing and button checks and also directly render whatever it needs to to D3D or whatever your doing.
So I'm guessing that the interface would be something like:
bool IMGUI::doButton(int x, int y, int width, int height, const char* text, IDirect3DDevice9* aDevice);
This implies that doButton() is also a rendering / drawing operation.
I can't really motivate "my style" (input separate from output) other than it being legacy. I can argue that there is no real point in doing user input before a logic tick or especially a rendered frame, since the user would be hard pressed to interact with a gui that is yet to be rendered...
I think that at some point I did the cache of "gui primitives" in order to be able to compose my rendering exactly as I wanted, but since everything I do is immediate mode anyway, there is no "scene graph", and I have complete control of where the gui stuff gets drawn anyhow.
So one could do something like this:
void Controller::update(IEventTarget& aTarget, IDirect3DDevice9* aDevice)
{
//draw all 3d stuff
draw(aDevice);
//do and draw all imgui interactions
if(gui.doButton(aDevice))
{
aTarget.changeSomething();
}
}
The big win is that there is no temporary cache inside your gui code at all, you are just rendering straight to the underlying API.
(Come to think of it, there are probably gains to be had by doing a certain amount of batching; drawing all rectangles for buttons first, and then all texts later, depending on your underlying API.)
Again, I've been assuming that this is the gist of what hObbE does (I know that the hUb IMGUI code works this way), but hObbE please confirm.
/johno
"you can't stop the change"
about IDs
I personally haven't used any kind of IDs at all for basic interactions like buttons. This because I don't care about the case "you have to have pressed down and released on the same widget for it to be a click".
I implement a widget click as "mouse button went up within the bounds of a widget". I have a basically global cache of all keystates (keys and mousebuttons alike) for 2 frames, the last and the current:
bool Input::isDown(const int aKey)
{
return myStates[aKey];
}
bool Input::wasPressed(const int aKey)
{
return !myStates[aKey] && myPreviousStates[aKey];
}
So it is "not down this frame and down last frame" that constitutes a "key up event".
Each doWidget() call in my guis just sample the global state directly; there are no events.
To implement edit boxes I pass and store (in the gui) a single pointer to the string that is currently being edited, which is used both for changing the string and also denotes the edit box instance that is currently active and being edited (as opposed to any others on the screen that aren't active). This is the classic Casey Muratori example.
/johno
"you can't stop the change"
Reference game
I thought I'd mention that I plan on doing a simple reference game and post full source on my website in order to exemplify this whole discussion about MVC and IMGUI.
Blakhouse came up with the idea of doing Pong, which I thought was a good idea as it is as simple as can be, so it will be something along those lines.
I'm thinking of using GDI+ for the rendering and one of Windows' simple sound-playing functions for sound.
Right now I'm in between machines (writing this in Safe Mode) but as soon as I'm up and running again I'll get to work on this.
/johno
"you can't stop the change"
imgui and state
well what i meant was of course that imgui is tightly coupled with state since user interactions probably leads to some kind of change in state.
"This is of course assuming that you don't already have a const & to the Model via View, as in my earlier examples."
I do but how does that differ from the const Model& sent to doinput?
ok i suspected that you did it like that. It feels a bit overkill at the moment. I think i'll go with the hobbe way. I could still cache stuff to draw and draw them at the end of the controller update. i really like the cleanliness of that because as you say there is no temporary cache.
currently i don't batch anything for simplicity but i'll probably need to at some point.
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
re ids and reference game
sounds great that id handling isn't needed. i'll just remove it for now. I thought it would be more useful since that tutorial used it.
sounds great with a pong game. i don't understand why you'd prefer to do it in GDI rather than d3d or opengl though
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
various topics
About the const Model reference:
I just meant that if you already have constant access to the Model (via View or singleton or whatever) you don't have to pass it to Controller::doInput() at all.
Again, the point of IEventTarget is to abstract the "target" for state-changing calls. My original intent was to allow for remote proxies (for networked games).
If you are just doing a local game, then there is no real need for constant access to Model throughout; you can simply use a writable reference and change things wherever you want to.
About the reference game:
I am thinking of using GDI+ for the reason that there is less overhead to get something like that running (less setup code) than for example D3D (I've never used OpenGL), and thereby less risk of confusing people.
A digression:
I've never really seriously considered making an abstract View, for example so you could have the same Model and Controller use different concrete visualization implementations, but I'm sure you could get that to work and it might even make your Controller code less visualization-implementation specific.
However, Astro-Creeps started off using my legacy 16 bit software-rendering / DirectDraw code (used in all my other games on hUb). For a while I had 3 parallel versions of that game - one that used DirectDraw, and one that used Direct3D 9 for 2d graphics (that one is on hUb now), and finally one that was really in 3d (but the gameplay was on a flat plane).
The shared code in those cases was only the library that contained the Model stuff. The DDraw version was on executable (and project), the 2d-D3D9 one was another executable (and project), and the 3d-D3D9 one was yet another executable (and project).
What I found was that the Controller code wasn't all that similar between the projects. If it had been I would probably have created an abstract View layer and put the Controller in a library as well, so that the three executables could share Controller code as well.
Again, I suspect you COULD make an abstract View layer, but you would probably have to write more code total to get the various visualization APIs to have a common interface, with plenty of adapter code in between.
However, there is something to be said about this approach (meaning having your Model in a library and potentially having multiple exes for various visualizations) when it comes to rapid prototyping. The experience of doing the port to full 3d was very interesting, because since there was a fully functional game running in there (the Model) the whole process felt like "uncovering the invisible game".
This really drove home the power of MVC (at least of making the Model truly independent of the other parts), and I think this kind of thing would be extremely useful for getting your game prototyped and working on a logical level before diving into (and being distracted by) the nifty gfx and sound stuff.
Note: I wanted to get the 3d version of Astro-Creeps up on hUb, but ran into trouble with the issue of 3d models of asteroids interpenetrating and looking strange because of the z-buffer. Since then I've realized that an orthographic projection might do the trick (along with placing each asteroid at a unique z-depth), but I haven't got around to it.
/johno
"you can't stop the change"
Ah i see. I don't think i
Ah i see. I don't think i understood that the const model was only useful due to preparation of a remote proxy before. I think i'd rather learn to keep that way though to see if it gets in the way of a local game or not. If not i could as well code that way to have experience needed for networked games later.
Interesting about the view. that would be ideal for tutorial purposes as anyone could just take a game source and implement whatever visualization they wanted for it.
Also interesting to hear that the controller was so different you had to have completely different controllers rather than sharing them. I guess the controllers are so close to the view that its natural.
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
I confirm!
I've not had the time to read the comments through but:
I confirm that that is my way
At some point I was worried that there would be rendering performance problems but as it seems there where none.
I also use no id's in my IMGUI
I disagree that menus are about state changes (in the model) the whole menu in TWTPB there are about two calls over the IEventTarget (start a game, input name). Most game menu stuff is actually changing settings in the view (input, resolutions, volumes, difficulty, etc).
...
another note on IMGUI and state
Reading this back again I think one thing needs to be made very clear. You wrote:
"well what i meant was of course that imgui is tightly coupled with state since user interactions probably leads to some kind of change in state."
I would say that the Controller and View are both tightly coupled to / dependent upon Model since they both read the contents of Model. Controller also of course writes changes to Model either directly or via the IEventTarget abstraction.
However, one of the main points of IMGUI is that you DON'T move state from your application into your "imgui context" to be STORED (which would be the retained mode thing to do).
To be fair, neither IMGUI or RMGUI can be said to deal with any kind of application specific data, rather they work with ints and floats and strings and things like that.
The main thing I want to stress is that while Controller and View are directly dependent upon your domain Model, your IMGUI can still be a "library", i.e. not specifically tied to your specific Model.
An example, as verbose as I can make it:
void Controller::doInput(IEventTarget& aTarget)
{
if(myView.myGui.doRadioButton(myView.myModel.myState == STATE_MENU))
{
aTarget.setState(STATE_MENU);
}
if(myView.myGui.doRadioButton(myView.myModel.myState == STATE_GAME))
{
aTarget.setState(STATE_GAME);
}
}
This is meant to show that there are two radio buttons with which you can select the current "state of the application".
The "gui context" as being part of the View. Each call to doRadioButton() requires that you explicitly pass state (whether or not the button is depressed) in order for it to display correctly. If the button was clicked on, the gui returns true and the Controller can change the state it wants to.
The main point here is that Controller is basically translating domain specific state (stuff from the Model) into the generic state (a boolean) that the IMGUI is using to draw a radio button. This is one of Controller's very important jobs.
In this manner you can have a more or less generic imgui library (based on some rendering API) that you can reuse between projects.
Another stab at this would be:
void Controller::doInput(IEventTarget& aTarget)
{
if(myView.doStateRadioButton(STATE_MENU))
{
aTarget.setState(STATE_MENU);
}
if(myView.doStateRadioButton(STATE_GAME))
{
aTarget.setState(STATE_GAME);
}
}
In this case I have conceptually mashed together the View and the IMGUI context, intending that there is no dilineation between the View and the gui. Thereby I can have a more high-level and domain-specific interface for the gui / widget calls, in that View knows about the Model and knows what a "state radio button" is and what state it needs to look at (Model::myState) in order to draw the button in the correct way.
Neither way is really wrong or right, it depends more on the level of reuse you're aiming at. The important thing is still that Model is unaware of any of this stuff, and that any implementation of a gui should render based on the actual state of the Model, whether it is being "translated" as in the first example, or "looked up directly" as in the second.
/johno
"you can't stop the change"
agree with hObbE
Yes, I agree that "state about what menu is active" isn't something I would put in the Model. I was just going along with Benny's example (which is in no way "wrong", it's just a matter of style).
Again we're back to the "purist" discussion about "what is state" and "where does state go?"
My razor / litmus test is basically: "does the Model care about this state?". When it comes to "front ends / menus" I would say that no, the Model does not care or need to know about this.
That kind of decision results in the need to keep state about the "current menu" somewhere else, and I would put it in the relevant controller (in Bloom it is in IdleController, which handles all the pre-game screens).
Consider for example an integrated editor (something that I think I will have to include in my proposed reference game for clarity). I typically have a bunch of different editors, sometimes with several "tools" each. These need to be selectable one at a time, and thus since I use IMGUI I need at least a little integer or a pointer that tells me which "editing tool" is currently active.
All of this stuff gets encapsulated somewhere in my Controller heirarchy; remember that I use hierarchies of Controllers, and I would say that my "tools" are also Controllers in that they map input to state changes as well as draw specific stuff.
IEventTarget does indeed reflect the existence of editors by including methods for changing state in Model, and thus Model needs to implement these methods in order to be "editable", but that is the whole extent of it.
One could of course omit IEventTarget and have the editors write directly to Model state, making Model more truly independent of when and how it is changed. This would probably shift lots of the stuff that I currently have in Model (for a game like Bloom) to the Controller responsible for changing that stuff. However, I tend to like to keep stuff like range checking in the class (Model) responsible for the data being changed, thereby being sort of OOPish about it all...
/johno
"you can't stop the change"
I regret my poor choice of
I regret my poor choice of words here. I had no intention of placing state in the imgui. i meant that interactions usually at some point end up changing data. at this point you're preaching to the choir as i would place data in either the model or controller/view depending on the razor you mention
still, always nice with all the extra info. thanks guys!
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
Deferred rendering
How do you store and render imgui later? Do you dynamically create new objects every frame and free them at the end of it or do you have pools or such?
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
hi Well I do not store any
hi
Well I do not store any state at all in my imgui classes, in fact my imgui functions are all static to prevent me from that
And my implementation does not completely divide the view from the controller...
some code to explain it
void controller::EditorController::DoEditorguiInput(const model::Model &a_model, IEventTarget &a_target) {
if(IMGui::DoButton(0,0, "Save", a_model.HasBeenChanged() == true) == IMgui::LeftMouseButtonClicked) {
a_target.DoSaveLevel()
}
}
so the dobutton above does render the button and all state variables must be sent to it...
IMGui::MouseFeedback DoButton(int a_x, int a_y, String a_text, bool a_isEnabled) {
//check if mouse is on text and if any buttons are pressed
//draw button
//return feedback depending on mouse state...
}
deferred rendering
In D3D I use software vertex buffers for the button rects (fixed size array = max number of rects), and draw them using DrawIndexedPrimitiveUP().
Same thing for text strings, but the structures stored there are text string and a position (also fixed sized array = max number of texts).
To be completely flexible you would have to do dynamic allocation of the stuff you need per frame (or some kind of growing array), but I didn't like that so I went with fixed sized arrays and assertions in debug mode to check if I went out of bounds. Even if you don't have assertions going out of bounds is easy to see since some of your gui just doesn't render, and the fix is as simple as just changing the constant size of the array and recompiling.
/johno
"you can't stop the change"
ok you use the provided
ok you use the provided facilities for the graphics api then, not caching in your own classes so to speak. I actually went ahead and created new stored copies of all objects to be drawn later but there are a lot of copying and new-delete-ing in there so i'll probably change it to use opengl display lists later and a similar structure for text like you mentioned.
---------------
Solid Core Entertainment
Developer of Roadclub and Sense: Survival Prelude (Developer blog)
Post new comment