Re: Text terminal rendering design



H. S. Lahman wrote in
jk13i.28149$b67.15362@trnddc06:">news:jk13i.28149$b67.15362@trnddc06:
I think the 2D array is an important conceptual entity in the
UI problem space; it is a fundamental element of the UI design.
The management of that array's content by the client is quite a
different concern than actually rendering it in the display de
jour. So I would be inclined to make 2D Array a peer object of
Terminal, Symbol, etc.

That is an interesting idea, but it has some issues that are
concerning me. The array and the terminal are more tightly tied
together than having them as separate objects suggests: there is
exactly one array for every terminal and in some cases modifying the
array causes the UI to be immediately modified. This is rather beyond
my control since I am not implementing the array in some cases, such
as with PDCurses where the array is internal to that library.

As it happens, most text terminal libraries seem to have a
distinction between modifying the array and doing the drawing, such
as requiring a call to 'refresh()' before changes to the array are
made visible, but that might not always be the case.

The client would talk to 2D Array to define its content, but it
would talk to somebody else to trigger the rendering once the 2D
Array is properly initialized. That somebody else would manage the
rendering by "walking" the 2D Array (e.g., getNextSymbol).

In cases where the array is out of my hands, to do what you are
suggesting I would have to create a redundant array and use it to
modify the array supplied by the low-level library.

The client calls methods of a SymbolFactory object to create a
Symbol and then it calls a method to write the symbol to the
Terminal's array at some coordinates. That method is as simple as
put(x,y,Symbol).

This sounds like 2D Array has actually four dimensions: {symbol
ID, x coord, y coord, Symbol reference}. The client provides a
symbol ID and the coordinates while somebody else (SymbolFactory)
provides the Symbol reference.

I do not understand what you intend to be the difference between
symbol ID and Symbol reference. In my current design, a Symbol object
holds whatever is needed to render the Symbol: in some cases a int
that can be given to PDCurses and in some cases a reference to a
Drawer object that knows how to render the symbol to some graphics
library.

So once we have a Symbol object, what could we need an additional
symbol ID value for? I cannot clearly see what you intend its use or
purpose to be in the array.

I assume the client provides additional information to create one
indirectly, or simply provides an identifier to extract it from a
database. In any case, there has to be a mapping between the
symbol ID the client provides and the actual Symbol.

Let me see if I understand this correctly. The symbol ID is an
abstract term for an instance of however the client represents a
symbol. The client does not give such a thing to the Array; it would
be useless there since the Array has no responsibility for
interpreting whatever strange representation the client uses. The
client gives the symbol ID to the SymbolFactory by using whatever
symbol construction interface the factory offers, as well as giving
coordinates so that the SymbolFactory can initialize the relationship
between the Array and the Symbol.

So far I see the following interface messages that must be
supported:

- extract a symbol from a database (via symbol ID)
- create a new symbol

I am unclear on the difference between these two messages. In my
current design, creating a symbol is done by sending messages to a
SymbolFactory which offers a variety of methods for translating the
desire of the client into something that can be rendered. These
include more complex and varied messages than those two, such as
translating an arbitrary symbol generated by a different means into
the closest symbol that can be generated by this factory.

Isn't 'extract a symbol from a database' just one of the many more
explicit versions of the general 'create a new symbol' message?

- nest symbols within a larger symbol (BigSymbol)

I'll have to put worrying about more advanced features on hold for a
while until I figure out the design of the simpler features.

- define coordinates for a symbol's display

This doesn't seem to work as a separate message. We aren't allowing
the client to handle references to Symbols, so it has to specify the
coordinates within the same message where it is asking for the Symbol
to be created.

- identify a list of symbols (2D Array)

I am not sure what you mean by 'identify' here. A 2D Array of symbols
is innate in any terminal implementation and it does not need the
client to identify it. For example, it will usually not be reasonable
to allow the client to switch back and forth between several 2D
Arrays.

For example, the message the client sends that triggers the
rendering may actually have the semantics of "I'm done defining
symbols" in the client's context.

Indeed, that is exactly the semantics that are usual in this domain.
The idea is that text terminal rendering module or library is only
responsible for faithfully representing the array of symbols that the
client has constructed to the user. The presence of a 'refresh()'
method to trigger a rendering is only incidental; the terminal could
fulfill its obligation without it by immediately rendering whenever
the array changes. 'refresh()' exists to allow the client to declare
that it is done modifying the array for the moment so now is a good
time to render.

If the terminal were to automatically render after waiting tenth of a
second after the most recent array modification, that would just be
another way to satisfy its obligations.

My second point is that these messages map to quite different
responses that involve quite different business rules and
policies. That is further complicated by the fact that the
rendering environment itself is complex and there may be
alternatives for rendering a given Symbol. So even if the
responses to the first three ("creation") messages were simple
enough to put in a single SymbolFactory object, one might not want
to do that because of the need to map into flavors of terminals.
IOW, I am still pushing on the theme of separation of concerns by
arguing that I would bet there are more objects needed.

I am aware that the various means of creating symbols have equally
various implementations, but that is not enough explanation for why
the factory might be better as several objects rather than one. In
the current design a factory is nothing but a stateless collection of
methods that presented to the client as means of building symbols for
later rendering. Splitting the one object into several means
delivering that collection of to the client methods as a collection
of objects rather than one object.

If there was a chance that I might want to vary the content of that
collection on the level of individual objects then I would
immediately use that design, but cannot see how that could happen.
All of the methods of each factory share one critical thing in
common: the produce symbols in a specific encoding. Two methods that
produce different encodings could never be used together. So each
factory object represents a collection of methods that are always and
only used together.

My main issue here is that there seem to be quite different
concerns here that could be delegated to make [Terminal] simpler:

- high level display control (init() and shutdown())
- managing the list of symbols to display
- managing coordinates (acquiring and mapping to Symbols)
- format conversions (ASCII/unicode -> terminal)
- Symbol instantiation
- Symbol rendering
- managing terminal configurations
- managing symbol nesting

You have delegated the mechanical details of Symbol instantiation
to SymbolFactory, but all the decision making is still in
Terminal.

I have also delegated format conversion into SymbolFactory, together
with Symbol instantiation. I am having trouble imagining it anywhere
else, though I am aware that you consider them to be separate
concerns.

All I see is that the client has one representation of a symbol and
the the terminal has another. The job of creating a Symbol object is
exactly the job of converting from the client's symbol representation
to the terminal's representation. After all, you cannot construct a
Symbol without knowing what it should contain, and if you know what a
Symbol should contain there is no way to give that knowledge to any
other procedure but by constructing the Symbol and passing it.

I see the following megathinker view of the tasks that need to be
done to solve the problem:
[...]
(2) Instantiate those symbols in memory in their "natural" form
(SymbolFactory).

(2A) Map them into the the data structures in (1).

(3) If necessary, reformat symbols to a form suitable to the
terminal (ConvertSymbol?). [This may be more than just
ASCII/unicode. For graphics environments there may a preferred
format for other data in a particular terminal environment (e.g.,
16-bit vs. 32-bit color).]

(3A) Replace the symbol mapping (2A) into the data structures in
(1).

I am fundamentally confused by what you are suggesting in tasks 2 and
3. What do you mean by "natural" form? It seems to be some sort of
intermediate form between the client's view of the symbol and the
terminal's view, but wouldn't that require a universal symbol
representation that could serve as an intermediate between any client
and any terminal?

Of course there would be huge benefits to having such an intermediate
representation, but there really is no such thing. Even Unicode does
not contain enough symbols to fill that role, and while an image
format could represent more than enough symbols, it would be massive
task to convert it to ASCII.

My current design allows the use of either ASCII or Unicode as an
intermediate format to allow conversions that are not supported any
other way, since I am fully aware that without intermediates the task
of general encoding conversions is impossible. However, I let each
encoding conversion procedure decide individually if it needs to use
an intermediate encoding because there are symbols that cannot be
faithfully or efficiently represented in any particular intermediate,
but can be easily represented by both the client and the terminal.

On the other hand, perhaps I am wildly misinterpreting the meaning of
"natural form".

The more complex those conversion operations are, the more I would
lobby for a different object to deal with conversions. B-)
Converting one symbol into another is a quite different thing than
instantiating one. In effect, whoever understands the conversion
rules could become a client of SymbolFactory; it extracts data
from the old symbol, converts it, and passes it to SymbolFactory
for instantiation as a new Symbol.

I am uncertain about that last step, the passing of the data from the
converter to SymbolFactory. How do you suggest I pass the symbol data
if not in a Symbol object? Do I need another class, SymbolData, that
represents symbol data just like Symbol does but is subtly different?
Why is the conversion object allowed to instantiate SymbolData but
not Symbol?

It seems unlikely that I correctly understand this, but let me
describe what seems to be the design you are advocating. The
SymbolFactory of my design is hidden from the client and the client
is given a SymbolConverter object. The client uses the
SymbolConverter the client calls a method of SymbolConverter that
looks something like this:

converter.convert(x, y, someSymbolRepresentation)

The SymbolConverter uses that call as a sign to convert the given
representation to the required representation. SymbolConverter has a
reference to a SymbolFactory object and once SymbolConverter has the
correct symbol encoding, it calls a method of SymbolFactory that
looks like this:

factory.instantiate(x, y, goodSymbolRepresentation)

Then SymbolFactory does nothing much but be a middle-man and use the
reference to 2DArray that it has to call this method:

array.putSymbolAt(x, y, goodSymbolRepresentation)

I feel that I am missing the separation of concerns that you are
trying to suggest. You seem to be saying that the work can somehow be
divided between a converter and a factory, but all I can imagine is
the above which leave the factory with nothing at all to do. It looks
like one atomic concern to me, impossible to separate.

You are correct that the client is not expected to collaborate
with the Symbol. In my current design the client is only
responsible for storing the Symbols that it wants to use and then
giving them to the Terminal when each symbol needs to be
displayed.

Then I would cut out the middleman and let SymbolFactory
instantiate the relationship directly between Symbol and Terminal.

But then the Symbol needs to be converted and constructed once for
every occurrance on the screen. The client is not collaborating with
Symbol, it is just passing Symbol on to the 2D Array, but that
doesn't mean that the client is useless in the process! Only the
client knows exactly where and when the Symbol needs to appear.

If we cut out the middleman in this case, we need the factory to know
how to instantiate the relansionships between the 2D Array and the
Symbol. Giving that information to the factory would be wildly
impractical, because we are not just talking about a pair of
coordinates. The Symbol might need to appear in many places. That
could be represented by a bitmap over the position in the array, but
even that is not enough because the relationship between the array
and the symbols varies over time. It's possible to do, but are you
really suggesting that I try a scheme like that?

At the risk of getting further off on a tangent, the factory
should always instantiate any relationships that the created
object should have when it is created. IOW, one should not pass
the reference back to the caller to let the caller instantiate its
relationships.

When you say that the factory should instantiate the relationships
that should exist when the product is created, you are strongly
suggesting that the factory is not responsible for instantiating
relationships at other times. Since the relationships between the 2D
Array and Symbols are frequently being instantiated and destroyed
over the execution of the application, who should be responsible for
making those instantiations at other times?

I am arguing that Terminal should not need to know what flavor of
Symbol it is accessing. It just navigates a relationship

1 R1 *
[Terminal] --------------- [Symbol]

to whatever is on the other end of R1. It is somebody else's job
to make sure the right flavor of [Symbol] is there (i.e., has the
responsibilities and implementations needed for the
collaboration).

What exactly do you suppose Terminal will do once it has navigated R1
to get to the Symbol? I can imagine that there would be a
symbol.draw() method that would cause the symbol to draw itself, or
else a relationship to a DisplayStrategy that has a similar method,
but what should the arguments of that method be?

In a PDCurses implementation, all it would need would be coordinates
but in other implementations greater context information could be
required. When I let Terminal hold ultimate responsibility for
drawing, it is free to draw directly or call a method of its choosing
with all the needed information supplied as arguments, but when I
design in a more clever and detailed way and specify in advance the
control path that drawing must take I am forcing the drawing to
happen blindly.

Imagine a draw(x,y) method that must be used for both graphical and
PDCurses terminal implementations. For the terminal implementation
the draw(x,y) method would be overridden to call the 'mvaddch()'
PDCurses library function.

For the graphical implementation it would have to be responsible for
actually drawing the symbol. Unfortunately, there I get stuck because
just (x,y) does not seem like enough information. Does (x,y)
represent a pixel on the screen, or the position of a symbol in the
array? How wide and tall should the symbol be drawn? The graphical
library probably gives me a new drawing context each time I need to
redraw the screen, but how does the draw(x,y) method get that? This
is why I say that I am drawing blindly.

Is it possible that we are causing problems by being too object
oriented?

Plus, in your diagram for the DisplayStrategy, you showed it being
many Symbols for each DisplayStrategy. That means that when the
strategy wants to draw it has to be passed the current Symbol, and
that means that it will have to know the subclass of Symbol to draw
it correctly. That is exactly what you said we were trying to avoid
in Terminal.
.



Relevant Pages

  • Re: Text terminal rendering design
    ... The management of that array's content by the client is quite a ... The array and the terminal are more tightly tied together than having them as separate objects suggests: there is exactly one array for every terminal and in some cases modifying the array causes the UI to be immediately modified. ... This is rather beyond my control since I am not implementing the array in some cases, such as with PDCurses where the array is internal to that library. ... The fact is that to solve the problem you need a 2DArray for the terminals where you can't use PDCurses. ...
    (comp.object)
  • Re: passing a NULL pointer from vb6 to an ATL method
    ... Is all this necessary if the client and server are in the same ... local/call_as is necessary fior marshaling. ... Dim array as Double ...
    (microsoft.public.vc.atl)
  • Re: passing a NULL pointer from vb6 to an ATL method
    ... Is all this necessary if the client and server are in the same apartment? ... Dim array as Double ...
    (microsoft.public.vc.atl)
  • Re: To serialize or not to serialize?
    ... array, sending smaller portions of data in several times... ... So, when the client asks for some data, the web service queries the ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: More IOCP Winsock!
    ... Do I make an array of the ... "Arkady Frenkel" wrote: ... accepton server) and when remove when client disconnected, ...
    (microsoft.public.win32.programmer.networks)