... but in e.g. Allegro (which is obviously built on top of whatever the platform supports, in my case OpenGL) they do kind of the same, but there's actually a good reason for it, namely that all high-performance graphics APIs are done that way. Why? Well, graphics hardware functions very differently to a regular computer and you (the programmer) really need to be aware of that and think in terms of the underlying mechanism -- if you e.g. try to blit from several hundred character-sized textures to the display, then you're going to have a bad time. (What you must do is create a "font atlas" which contains all the characters and use that for blitting from. Etc. Generally speaking 'switching states' is slow on GPUs because of very high latency. You really just want to fill the pipeline.)
The problem is much lesser in programming graphics because the "display list" only sticks around for a single frame and then you start over. (Also known as "immediate mode".) For a game like Angband, that's pretty simple to manage because the scope is very limited -- I think the entirety of the code which does this in my T2 branch is about 50-100 lines? I'm pretty sure I can eventually get rid of the "damage" tracking that the term structure does -- leaving me with a simple 2D array of char+attribute... which might as well be inside the frontend code. (In fact the frontend code already has such a structure because I want it to be able to redraw without having to call back into the term code.)
Just to be clear: There are much better ways to manage the global drawing context which al_set_target_bitmap or whatever just hide under the carpet, such that you could actually have the compiler check your work, but they require pretty advanced type systems which C in the very least doesn't really support. They'd also get pretty hairy in C++, I imagine. Since most of the graphics APIs target C or C++, that's what we get.
Interesting -- I hadn't thought of doing it this way, but I think I'm going for a design where there's no "current target". All drawing methods must have an explicit target term where they will draw. (That will usually come from a formal function parameter, but obviously there will a top-level dispatching coming from the "window options", e.g. "what do I draw in which window".)
I'm not saying that the "2D grid" is a bad abstraction -- it's the intermingling of concerns of the event loop, drawing to the screen and keyboard input into one big ball of mud where it's really unclear what's responsible for what (and how 'many' there are of each).
I'm not convinced in any way that tracking "damage" at a single-character level is a useful thing -- just redraw from scratch for every frame[1]. An even reasonably modern GPU should be able to draw Angband at several hundred (if not thousands) of FPS.
(The exceptions here would be old frameworks where this type of drawing is not really supported very well. My experiences in SDL2.x and Allegro 5 suggest that at least there's no problem there.)
[1] Here's another mismatch between that UI code and the modern world. You really want do just draw on every frame (if there have been any changes) and the Angband/T2 code contains sleep, etc. in various places in the code. These could end up being pretty non-trivial to avoid because changing the structure around an event loop might end up being difficult... I'm not quite giving up yet because it might be reasonable to with coroutines or similar.
EDIT: Anyway, this is ranting at this point... .
The problem is much lesser in programming graphics because the "display list" only sticks around for a single frame and then you start over. (Also known as "immediate mode".) For a game like Angband, that's pretty simple to manage because the scope is very limited -- I think the entirety of the code which does this in my T2 branch is about 50-100 lines? I'm pretty sure I can eventually get rid of the "damage" tracking that the term structure does -- leaving me with a simple 2D array of char+attribute... which might as well be inside the frontend code. (In fact the frontend code already has such a structure because I want it to be able to redraw without having to call back into the term code.)
Just to be clear: There are much better ways to manage the global drawing context which al_set_target_bitmap or whatever just hide under the carpet, such that you could actually have the compiler check your work, but they require pretty advanced type systems which C in the very least doesn't really support. They'd also get pretty hairy in C++, I imagine. Since most of the graphics APIs target C or C++, that's what we get.
Interesting -- I hadn't thought of doing it this way, but I think I'm going for a design where there's no "current target". All drawing methods must have an explicit target term where they will draw. (That will usually come from a formal function parameter, but obviously there will a top-level dispatching coming from the "window options", e.g. "what do I draw in which window".)
I'm not saying that the "2D grid" is a bad abstraction -- it's the intermingling of concerns of the event loop, drawing to the screen and keyboard input into one big ball of mud where it's really unclear what's responsible for what (and how 'many' there are of each).
I'm not convinced in any way that tracking "damage" at a single-character level is a useful thing -- just redraw from scratch for every frame[1]. An even reasonably modern GPU should be able to draw Angband at several hundred (if not thousands) of FPS.
(The exceptions here would be old frameworks where this type of drawing is not really supported very well. My experiences in SDL2.x and Allegro 5 suggest that at least there's no problem there.)
[1] Here's another mismatch between that UI code and the modern world. You really want do just draw on every frame (if there have been any changes) and the Angband/T2 code contains sleep, etc. in various places in the code. These could end up being pretty non-trivial to avoid because changing the structure around an event loop might end up being difficult... I'm not quite giving up yet because it might be reasonable to with coroutines or similar.
EDIT: Anyway, this is ranting at this point... .
Comment