From: randyhyde on
Hi All,

This is really for Paul Panks, but the programming technique is
sufficiently general and interesting that I will post it here.

Paul needs the ability to put status information on the screen in a
fixed location. This is difficult to do under Windows and Linux in a
portable fashion, so I am offering an alternate solution: multiple
windows.

Rather than attempting to put the status message at the same point on a
screen that is scrolling by (and whose buffer is at an unknown point
under OSes such as Linux), a cleaner solution is to open up two windows
- one for the normal game (that scrolls) and one for the status window
(that does not scroll, and upon which the game writes status
information to specific spots on the screen). Because the status
window never scrolls, it is easy to control where stuff is written to
it (in a portable fashion) and still provide the usual user interface
in the main game window.

The scheme I am providing uses "memory-mapped files" under Windows and
Linux to create a "shared memory" window through which the program can
exchange data. The idea is as follows:

1. you run a "statusConsole" application whose sole job is to read data
from the shared memory area and write that data to the status window.

2. the main game program writes data to the shared memory block that it
wants the statusConsole application to display in the status window.

This is all fairly straight-forward. The only catch is that the user
has to run both applications (in different console windows) for this
process to work properly. Well, actually, there is one other *big*
catch. Because the two processes have different address spaces, you
cannot pass pointers between the two processes. And HLA strings *are*
pointers, so it's important that you pass the actual string (character)
data between the processes rather than pointers to this character data.
But other than this issue, using the two systems is very
straight-forward.

The following is a sample application that demonstrates this:

----- statusConsole.hhf ------ Header File ------


#if( !@defined( statusConsole_hhf ))
?statusConsole_hhf := true;

const
sharedEBX :text := "(type sharedData_t [ebx])";


// The following macro is used to declare string objects
// in the shared data area. This macro reserves storage
// for the maxlength, length, zero-terminator, and
// character data fields needed by the HLA string format.
// It also reserves some extra storage so we can guarantee
// that the string data is dword-aligned.

#macro sharedStr( _maxLen_ );
char[ (_maxLen_+20) div 4 * 4 ];
#endmacro


// The following data structure is the template for
// the shared memory area. Declare any variables you want
// to pass between the two processes in this record.
//
// Note: no strings and no pointer objects. For string
// objects, use the sharedStr type above and initialize
// these strings in your two processes (see the example
// code for details).

type

sharedData_t:
record

// The current example shares two variables
// between the two processes, a string whose
// length is a maximum of 256 characters and
// an integer value:

statusString :sharedStr(256);
currentScore :int32;

// Put any other shared variables here:

endrecord;

pSharedData_t :pointer to sharedData_t;

#endif


--- statusConsole.hla ---- status program source file ----

program statusConsole;
#include( "stdlib.hhf" )
#include( "console.hhf" )
#include( "w.hhf" )
#include( "statusConsole.hhf" )


static
statusStr :string;

shared :pSharedData_t;
mmFile :mmap;


begin statusConsole;

// The statusConsole program, which must be run first,
// is responsbile for creating the memory-mapped file
// in the file system:

mmFile.create();
mmFile.openNew
(
"advGameSharedData.dat",
@max( @size( sharedData_t ), 4096)
);
mov( mmFile.filePtr, eax );
mov( eax, shared );

// Zero out the shared memory area:

mov( eax, edi );
xor( eax, eax );
mov( @size( sharedData_t ), ecx );
rep.stosb();

// Initialize the string pointers into the shared data area.
// The call to str.init initializes the string you pass
// as the first parameter to the length specified by the
// second parameter. This function returns a pointer to
// the string data that you should store into a local
// STRING variable to use to manipulate that string.

mov( shared, ebx );
str.init( sharedEBX.statusString, 256 );
mov( eax, statusStr );


// Sit in a loop, forever updating the console info

forever

mov( shared, ebx );

// Clearing the screen causes flicker, so you
// may want to do something else here:

console.cls();

// Position the cursor and display the status
// string:

console.gotoxy( 0, 0 );
stdout.put( statusStr );

// Position the cursor and display the current score:

console.gotoxy( 0, 10 );
stdout.put( "Current Score: ", sharedEBX.currentScore );

// Update the display once every 500 ms:

w.Sleep( 500 );

endfor;



end statusConsole;



---- Main Game Program source file -----------

program main;
#include( "stdlib.hhf" )
#include( "console.hhf" )
#include( "statusConsole.hhf" )


static
shared :pSharedData_t;
mmFile :mmap;
statusStr :string;
inputLine :str.strvar( 256 );
statusLine :str.strvar( 300 );




begin main;

// The following code assumes that you've run the
// statusConsole application in a separate window
// *before* executing this program:

mmFile.create();
mmFile.open( "advGameSharedData.dat", fileio.rw );
mov( mmFile.filePtr, eax );
mov( eax, shared );

// Initialize the status line string. Same as
// for the statusConsole app except we also
// initialize the string data with a default
// string.

mov( eax, ebx );
str.init( sharedEBX.statusString, 256 );
mov( eax, statusStr );
str.cpy( "(no status)", statusStr );

// "Play" the game:

forever

stdout.put( "Enter a command (ctrl-C to quit): " );
stdin.gets( inputLine );
str.put( statusLine, "You entered: '", inputLine, "'" nl );

mov( shared, ebx );
str.cpy( statusLine, statusStr );
inc( sharedEBX.currentScore );

endfor;



end main;



To try out this code, compile both modules, open two console windows,
run statusConsole in one window *and then* run the main program in the
other window. When you type lines in the main window, the text should
appear in the status window. If you type enough lines, the main window
will begin to scroll, but the status window will be unaffected.

Of course, there is a lot of clean-up to do here. It would be nice to
automatically run the statusConsole program from the main program (a
quick call to os.system could do that, though getting it to run in a
new window in an OS-independent fashion might be a chore). Also, rather
than simply refreshing the status window every 500 ms, you might also
try passing an "update available" variable to the status window that it
can use to determine when it should update the window. Clearly, the
code could be a bit more robust. I've not tried to run this code under
Linux. It *should* work, but no guarantees).
Cheers,
Randy Hyde

From: Frank Kotler on
randyhyde(a)earthlink.net wrote:

....
> I've not tried to run this code under
> Linux. It *should* work, but no guarantees).

Nope. Gas complains about "junk '@4+0' after expression".
The offending line...

call dword ptr [__imp__Sleep@4+0] /* Sleep */

....isn't going to work in Linux, I don't think, even if I
get Gas to swallow it... I don't *see* a portable "sleep"
function in the library (I *thought* that "w" looked
suspicious). We've got "linux.nanosleep", so something can
probably be worked out...

I may give a shot at producing a "nanosleep" version of
this, 'cause I don't quite get how it's supposed to work...
When you say run it in "two windows", do you mean like F1
and F3 (F2 is busy running the X server)? Or are we talking
about two "xterms" - intended to be on the screen at the
same time? I don't think I like either much, as a solution
to Paul's problem, but as an example of "interprocess
communication using mmapped files", I think I can learn from
it! So, once again, "Thanks, Randy!"

Best,
Frank
From: Frank Kotler on
Frank Kotler wrote:

> I may give a shot at producing a "nanosleep" version of
> this, 'cause I don't quite get how it's supposed to work...

Oh, my! I cobbed together a "nanosleep", which may not be
right, but at least it assembles. Running the result gives
me "Bus Error". That's a new one on me (*told* ya I had a
lot to learn about this stuff!). I don't *think* it's my
homemade "nanosleep"... Seems to be happening before the
"cls". Strace shows "old_mmap" returning what looks like a
reasonable value, and SIGBUS right after that... Perhaps in
the... yes, ALD says it's happening in the "rep stosb"...

The "main" part of the program assembles without complaint.
Running it gives Memory Access Violation (HLA exception) -
not unexpected - the other thing's supposed to be running
first. If I type some arbitrary stuff into the zero-length
file left by "statusconsole", it runs - repeatedly prompting
for input - so I think that part's okay.

Bus Error... Bus Error...

This cat's sitting on a bench in Washington Square Park,
'bout three o'clock in the AM, you dig? Lady come up to him
and says, "Does the crosstown bus run all night?"

And he say, "... doo-dah, doo-dah..."

Later,
Frank
From: randyhyde on


Frank Kotler wrote:
> randyhyde(a)earthlink.net wrote:
>
> ...
> > I've not tried to run this code under
> > Linux. It *should* work, but no guarantees).
>
> Nope. Gas complains about "junk '@4+0' after expression".
> The offending line...
>
> call dword ptr [__imp__Sleep@4+0] /* Sleep */
>
> ...isn't going to work in Linux, I don't think, even if I
> get Gas to swallow it... I don't *see* a portable "sleep"
> function in the library (I *thought* that "w" looked
> suspicious). We've got "linux.nanosleep", so something can
> probably be worked out...


Duh....
Maybe the #include( "w.hhf") should have been my first clue...
Okay, just swap the w.Sleep call for a linux.sleep call and specify 1
(second) rather than 500 (ms). Of course, include linux.hhf rather than
w.hhf, too.
Cheers,
Randy Hyde

From: T.M. Sommers on
Frank Kotler wrote:
>
> Oh, my! I cobbed together a "nanosleep", which may not be
> right, but at least it assembles. Running the result gives
> me "Bus Error". That's a new one on me (*told* ya I had a
> lot to learn about this stuff!).

Bus errors usually mean that the program has tried to access an
invalid memory location, as opposed to a valid memory location
that you do not have permission to access, which gives a
segmentation fault. The most common cause is trying to
dereference a null pointer, but it could also be caused by a
misaligned access.

--
Thomas M. Sommers -- tms(a)nj.net -- AB2SB