command queue structure help?

Discussion in 'Ruby' started by mouse_059, Jul 25, 2007.

  1. mouse_059

    mouse_059 Guest

    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
     
    mouse_059, Jul 25, 2007
    #1
    1. Advertising

  2. mouse_059

    Greg Guest

    # 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
     
    Greg, Jul 25, 2007
    #2
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Paul L. Du Bois

    Queue.Queue-like class without the busy-wait

    Paul L. Du Bois, Mar 24, 2005, in forum: Python
    Replies:
    29
    Views:
    1,063
    Antoon Pardon
    Apr 4, 2005
  2. Russell Warren

    Is Queue.Queue.queue.clear() thread-safe?

    Russell Warren, Jun 22, 2006, in forum: Python
    Replies:
    4
    Views:
    690
    Russell Warren
    Jun 27, 2006
  3. Kceiw
    Replies:
    3
    Views:
    1,006
    Jim Langston
    Mar 14, 2006
  4. Kris
    Replies:
    0
    Views:
    493
  5. mouse_059

    command queue structure help?

    mouse_059, Jul 25, 2007, in forum: Ruby
    Replies:
    0
    Views:
    93
    mouse_059
    Jul 25, 2007
Loading...

Share This Page