command queue structure help?

M

mouse_059

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
MATRIX1 =[
["iip1", "/dev/ttyr2", 0, 0],
["iip2", "/dev/ttyr3", 0, 1],
["iic", "/dev/ttyr1", 0, 2] ]

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

MATRIXES = [ MATRIX1, MATRIX2]

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 = []
end

def <<(data)
@store << data
end

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

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

class DisplayMatrix holds an array of machines and has routines to
flush an entire matrix, and wait for all machines to be finished.

class DisplayMatrix
def initialize(machines)
@machines = machines
end

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

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)
@name = name
@dev = open_and_init(port) # calls open, also uses Termios library

@q = []
@q.extend(MonitorMixin)
end

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

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

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

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
end

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

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


And then the main program where you can see all the objects created
cleanly:
begin
mxs = Matrixes.new

MATRIXES.each do |matrix_def|
ms = []
matrix_def.each{ |name, port| ms << Machine.new(name, port) }
mxs << DisplayMatrix.new(ms)
end

vis = BlinkVis.new(3)
while true do
thr = vis.run(mxs)
thr.join
end
end


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

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" of the
command from the "actual" command I'm sending to the Apple? (eg,

machine.enqueue( FLASH, A2_SPACE | A2_INVERSE ) instead of
machine.enqueue("F\xa0")
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
screen,
etc.

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!

-m
 
G

Greg

# you could do this
def enqueue( command, *args )
data =
case command
when KALEIDOSCOPE then kaleidoscope( *args )
when HLINE then hline( *args )
else fail "unknown command"
end

@q.synchronize{ @q << data }
end

#but then why wouldn't you just write
machine.enqueue( kaleidoscope( optional_starting_color ) )

# optional argument
def kaleidosope( starting_color=WHITE )
# ...
end

#also you may want to simplify your code
class Matrixes << Array
def flush_and_wait
threads = []
each{ |mx| threads <<
Thread.new{ mx.flush_and_wait } }
threads.each{ |t| t.join }
end
end



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
MATRIX1 =[
["iip1", "/dev/ttyr2", 0, 0],
["iip2", "/dev/ttyr3", 0, 1],
["iic", "/dev/ttyr1", 0, 2] ]

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

MATRIXES = [ MATRIX1, MATRIX2]

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 = []
end

def <<(data)
@store << data
end

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

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

class DisplayMatrix holds an array of machines and has routines to
flush an entire matrix, and wait for all machines to be finished.

class DisplayMatrix
def initialize(machines)
@machines = machines
end

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

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)
@name = name
@dev = open_and_init(port) # calls open, also uses Termios library

@q = []
@q.extend(MonitorMixin)
end

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

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

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

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
end

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

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

And then the main program where you can see all the objects created
cleanly:
begin
mxs = Matrixes.new

MATRIXES.each do |matrix_def|
ms = []
matrix_def.each{ |name, port| ms << Machine.new(name, port) }
mxs << DisplayMatrix.new(ms)
end

vis = BlinkVis.new(3)
while true do
thr = vis.run(mxs)
thr.join
end
end

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

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" of the
command from the "actual" command I'm sending to the Apple? (eg,

machine.enqueue( FLASH, A2_SPACE | A2_INVERSE ) instead of
machine.enqueue("F\xa0")
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
screen,
etc.

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!

-m
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,965
Messages
2,570,148
Members
46,710
Latest member
FredricRen

Latest Threads

Top