Forum: Ruby command queue structure help?

Announcement (2017-05-07): is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see and for other Rails- und Ruby-related community platforms.
7e2f30514a91c9f000cd9b7f0b097eab?d=identicon&s=25 mouse_059 (Guest)
on 2007-07-25 08:55
(Received via mailing list)
Greetings.  I have been studying Ruby, have the pickaxe and Ruby Way
(Fulton).  Studying for a long time as I am a diehard C/assembly
programmer and it is very hard to get out of the data-driven mindset.
I believe to have finally found my killer application for Ruby (not
rails, or the web.)   I am however unfamiliar with OOP-isms and am
trying to come to grips with them.  Any assistance or direction anyone
could help me with, I appreciate!

PS:  The first time I coded this, it worked without a hitch.  A real
testament to Ruby.

The application:  Picture a grid of NTSC monitors controlled by Apple
II computers.  Each Apple II has a serial card in it, hooked up to a
portmaster, connected to a LAN and showing up on the local system (the
Console) as virtual ttys a la /dev/ttyr1, etc.  The Apples are running
custom assembly routines, triggered by a write to the serial port from
the Console to the tty port.  (I figured Ruby wouldn't be able to
handle this kind of IO.  Dead wrong, me!)  The routines make the Apple
monitor do different things - I can Fill the screen with a letter,
Draw a horizontal or vertical line, Display a compressed image stored
on the machine, etc.  For synchronization, when the Apple is done, it
sends a ACK bit to the serial port.

Here is what I have now, that works:
#name #port #y value #x value
  ["iip1", "/dev/ttyr2", 0, 0],
  ["iip2", "/dev/ttyr3", 0, 1],
  ["iic",  "/dev/ttyr1", 0, 2] ]

MATRIX2 = [ ["iie", "/dev/ttyr5"] ]


class Matrixes is a simple array (@store = []) and holds each
DisplayMatrix.  Each DisplayMatrix has its own group of computers and
may have different properties - for example the first three machines
are black and white, and in a row, so they make up a x,y display (40 *
3) by (24 * 1).  The second matrix has one machine in it and is
color.  A visualization can receive any number of these matrixes.

class Matrixes
        def initialize
                @store = []

        def <<(data)
                @store << data

        def each
                @store.each{ |mx| yield mx }

        def flush_and_wait
                threads = []
                @store.each{ |mx| threads <<{ mx.flush_and_wait } }
                threads.each{ |t| t.join }

class DisplayMatrix holds an array of machines and has routines to
flush an entire matrix, and wait for all machines to be finished. It
also knows things about its structure (for example it knows the
maximum horizontal and vertical resolution of the "virtual" text

class DisplayMatrix
        def initialize(machines)
                @machines = machines

        def flush_and_wait
                threads = []
                @machines.each{ |m| threads <<{ m.flush_and_wait } }
                threads.each{ |t| t.join }

class Machine is one machine, and does all the communication with the
serial port and keeps the object in a current state.  It receives
command requests and stores them on a queue.

class Machine
        def initialize(name, port, y, x)
                @name = name
                @y, @x = y, x
                @dev = open_and_init(port) # calls open, also uses
Termios library

                @q = []

        def enqueue(data)
                @q.synchronize{ @q << data }

        def flush_and_wait
                @q.synchronize do
                        @q.each{ |q| @dev.syswrite(q) }

                        readyfd = select([@dev], nil, nil)
                        raise "select on our fd didn't yield it?" if
readyfd.flatten.include? @dev
                        @dev.sysread(100) #read & toss acknowlegement,
right now simply a

Here is how I have a visualization currently.  You will see I can make
a visualization with custom parameters - in this case how many times
to loop it.  Other variables I can think of are how long to flash it,
which character to flash it with, etc.  So I must "create" a new
FlashVis object based on custom parameters and how I want to run it.

Queueing the letter F, and then an ascii SPACE, makes that Apple II
have a white on black screen.
Queueing the letter F, and then hex A0, blanks the Apple II screen (it
is an apple II normal space).

When I do a flush on the entire visualization matrix I wait for all
Apples to be done.  This creates O(n) threads as you can see, but
seems to have negligable performance impact.

# BlinkVis simply blinks the Apple II screens
class BlinkVis
        def initialize(count)
                @count = count

        def run(mxs)
                return do
                        @count.times do
                                mxs.each{ |mx| mx.queue_to_all("F ") }

                                mxs.each{ |mx| mx.queue_to_all("F
\xa0") }

And then the main program where you can see all the objects created
        mxs =

        MATRIXES.each do |matrix_def|
                ms = []
                matrix_def.each{ |name, port, y, x| ms <<, port, y, x) }
                mxs <<

        vis =
        while true do
                thr =

*****************(end of program code)****************

A few things are apparent about this and I must say - FIrst of all
this code does exactly what my C code does with about 10x less the
amount of lines due to ruby's excellent hierarchical error passing and
excellent array support, as you can imagine eliminating each "if fcntl
< 0 then error.. if tcgetattr < 0 then error.. if select < 0 then
error.. if read < 0 then error.." took a crap load of code away.
Secondly the code is extremely easy to parse.

Now that it does what my server coded in C does, I would like to
know:  What is the best way to abstract away the "intention" or
"notion" of the command from the "actual" command I need to
send to the Apple?  (eg,

machine.enqueue( FLASH, A2_SPACE | A2_INVERSE ) instead of
machine.enqueue( FLASH, A2_SPACE | A2_NORMAL ) instead of
machine.enqueue("F ")

EG: The 6502 code also prints vertical lines "V" and horizontal lines
"H" with three parameters: x from 0 to 39, y from 0 to 23, and
length.  It prints a compressed text screen block "E" with parameters
which char to use for the light bits, which char for the dark bits,
and the block.  It will make a kaleidoscope in lo-res graphics by
passing "K".  I can picture many more modes I will program in 6502 -
for example, printing text in varying sizes and fonts on the hi-res

machine.enqueue( KALEIDOSCOPE, optional starting_color )
machine.enqueue( HLINE, starty, startx, length, optional color )
machine.enqueue( TEXT, "The quick brown fox", FONT_BLA, 16 )

So with all these different commands, and parameters, what is the best
way to "enqueue" one of these commands to the machine for sending/
updating?  And I would like to be able to handle different kinds of
Machine in the future - it could be an Apple II, or it could be a dumb
terminal, or possibly a NES system with custom cartridge & serial
connection.  In each case the output to the machine would be
different.  The way I have done it so far seems to imply I can do what
I am asking here.  (And a big pain in the butt in C, which is why I
stopped and did this!!)  Ruby example code would be helpful!

PS: If any of you are II fans and would like to use this I will be
puting a webpage together with the assembly and all necessary
materials.  II Infinitum!

This topic is locked and can not be replied to.