Monday, January 30, 2012

Tables and labels

A table does not have to be operated in a procedural way. It can be plugged into the the scheduler machinery. Whenever a table is created, two labels are created with it.

The input label is for sending the modification rowops to the table.  The table provides the handler for it that applies the incoming rowops to the table. The output label propagates the modifications done to the table.  It is a dummy label, and does nothing by itself. It's there for chaining the other labels to it. The output rowop comes quite handy to propagate the table's modifications to the rest of the state.

Note that the rowops coming through these two labels aren't necessarily the same. If a DELETE rowop comes to the input label, referring to a row that is not in the table, it will not propagate. If an INSERT rowop comes in and causes another row to be replaced, the replaced row will be sent to the output label as a DELETE rowop first.

Well, if you need to look up records from the table, the look-ups are still done in the procedural way by calling the table methods.

So, let's make a version of "Hello, table" example that passes the records through the labels. Since it will print the information about the updates to the table as they happen, there is no more use having a separate command for that. But for another demonstration let's add a command that would clear the counter. And, without further introduction, here is the code:

my $hwunit = Triceps::Unit->new("hwunit") or die "$!";
my $rtCount = Triceps::RowType->new(
  address => "string",
  count => "int32",
) or die "$!";

my $ttCount = Triceps::TableType->new($rtCount)
  ->addSubIndex("byAddress",
    Triceps::IndexType->newHashed(key => [ "address" ])
  )
or die "$!";
$ttCount->initialize() or die "$!";

my $tCount = $hwunit->makeTable($ttCount, &Triceps::EM_CALL, "tCount") or die "$!";

my $lbPrintCount = $hwunit->makeLabel($tCount->getRowType(),
  "lbPrintCount", undef, sub { # (label, rowop)
    my ($label, $rowop) = @_;
    my $row = $rowop->getRow();
    print(&Triceps::opcodeString($rowop->getOpcode), " '",
      $row->get("address"), "', count ", $row->get("count"), "\n");
  } ) or die "$!";
$tCount->getOutputLabel()->chain($lbPrintCount) or die "$!";

# the updates will be sent here, for the tables to process
my $lbTableInput = $tCount->getInputLabel();

while(<STDIN>) {
  chomp;
  my @data = split(/\W+/);

  # the common part: find if there already is a count for this address
  my $pattern = $rtCount->makeRowHash(
    address => $data[1]
  ) or die "$!";
  my $rhFound = $tCount->find($pattern) or die "$!";
  my $cnt = 0;
  if (!$rhFound->isNull()) {
    $cnt = $rhFound->getRow()->get("count");
  }

  if ($data[0] =~ /^hello$/i) {
    $hwunit->schedule($lbTableInput->makeRowop(&Triceps::OP_INSERT,
      $lbTableInput->getType()->makeRowHash(
        address => $data[1],
        count => $cnt+1,
      ))
    ) or die "$!";
  } elsif ($data[0] =~ /^clear$/i) {
    $hwunit->schedule($lbTableInput->makeRowop(&Triceps::OP_DELETE,
      $lbTableInput->getType()->makeRowHash(address => $data[1]))
    ) or die "$!";
  } else {
    print("Unknown command '$data[0]'\n");
  }
  $hwunit->drainFrame();
}

Here is an example of input (in cursive) and output:

Hello, table!
OP_INSERT 'table', count 1
Hello, world!
OP_INSERT 'world', count 1
Hello, table!
OP_DELETE 'table', count 1
OP_INSERT 'table', count 2
clear, table
OP_DELETE 'table', count 2
Hello, table!
OP_INSERT 'table', count 1

The references to the input and output labels of a table are gotten with:

$label = $table->getInputLabel();
$label = $table->getOutputLabel();

The output label in this example then gets another label chained to it, the one that prints what is coming to it. The input label receives a translation of the commands coming from the user.

The inserts work in a fairly straightforward way. The deletes require only the key to be populated. Internally a delete really translates to a find, followed by the removal of the record if it was found (more on the details of that later).

As you can see in the  example output, when the first record for a key is inserted, it's just an insert. But when the second "hello" is received, the insert causes the old row to be deleted first, and produces a delete followed by the new insert on the table's output. Now, depending on what you want, just sending the consequent inserts of rows with the same keys, and relying on the table's internal consistency to turn them into updates, might be a good thing or not. Overall it's a dirty way to write but sometimes it comes convenient. The clean way is to do the explicit deletes first.

For the closing thought, creating these rowops through all the nested calls is fairly annoying, and could use some simplification like find() had with findBy().

No comments:

Post a Comment