From: Greg Date: 2007-07-25T23:55:05+09:00 Subject: Re: command queue structure help? # 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 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