Greg
7/25/2007 2:55:00 PM
# 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
On Jul 25, 1:45 am, mouse_059 <kmo...@gmail.com> wrote:
> 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