monotone-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Monotone-devel] responses to some IRC discussion of 'automate'


From: Nathaniel Smith
Subject: Re: [Monotone-devel] responses to some IRC discussion of 'automate'
Date: Wed, 9 Aug 2006 05:19:24 -0700
User-agent: Mutt/1.5.12-2006-07-14

On Mon, Aug 07, 2006 at 10:21:24AM +0200, Thomas Keller wrote:
> >def get_lots_of_head_info(mtn):
> >    branches = mtn.branches()
> >    heads = {}
> >    for branch in branches:
> >        heads[branch] = mtn.heads(branch)
> >    certs_for_head = {}
> >    for head_set in heads.itervalues():
> >        for head in head_set:
> >            certs_for_head[head] = mtn.certs(head)
> >    return heads, certs_for_head
> >
> >Easy to read and straightforward, and could actually be made even
> >shorter with some judicious use of list or generator comprehensions,
> >e.g.
> >    heads = dict((branch, mtn.heads(branch)) for branch in mtn.branches())
> >already gives you all branches and all heads, in one line, about as
> >efficiently as anything can :-).
> 
> Well, its nice to see that its that easy to do in Python, but its not 
> that easy in other languages. The amount of verbosity which is needed 
> e.g. in C++ with Qt is quite big, and because of Qt's "superior" signal 
> and slot handling one would need to couple the querying of each set with 
> each other, and this is very messy. I won't give you an example here, 
> but ask somebody else with Qt experience and he'll tell you the same 
> thing. Of course you might say now "Qt was your choice", well, right, Qt 
> was my choice, because its fast, consistent and otherwise very easy to 
> code for different platforms, but retrieving tiny bits of information 
> from different sources and/or different handlers even (and each mtn 
> automate command would need to get its own handler) is a major headache.

Oh, duh, for some reason I was thinking of Trac.  Sorry.

Right, using an async api is always more complicated than using a
synchronous api.  That doesn't mean it has to be messy, though...
and in the long run you probably don't want to end up adding a new
automate command (and parser) for every single combination of data you
ever want to get all at once.  So maybe what we need here is a better
API inside your monotone-calling library?

What we really want is to be able to say "please call 'automate
branches' for me, then fire a signal to give me the branches", etc.
So something like

  class BranchSelectionDialog : QWhatever
  {
    public:
      BranchSelectionDialog(Monotone & m)
      {
          m.get_branches(this, SLOT(got_branches(set<string> const &>)));
          // <open an empty window>
      }

      void got_branches(std::set<std::string> const & branches)
      {
          // <fill in the window with a list or tree widget with all
          // the branch names, then:>
          for (set<string>::const_iterator i = branches.begin();
               i != branches.end(); ++i)
              m.get_heads(this, SLOT(got_heads(string const &, set<string> 
const &)));
      }

      void got_heads(string const & branch, set<string> const & heads)
      {
          // <find the entry for 'branch' in the tree widget, and
          // stick entries for each head below it, then:>
          for (set<string>::const_iterator i = branches.begin();
               i != branches.end(); ++i)
              m.get_certs(this, SLOT(got_certs(string const &, foo const &)));

      }

      void get_certs(string const & revid, foo const & certs)
      {
          // <find all entries for 'revid' in the tree widget, and
          // use the cert data to fill in more details for them>
      }
  } 

That doesn't seem much worse than the python, really; and in fact it's
even better than the magic automate command that gives you all of that
information in one big lump, because the GUI fills itself in
gradually, and the user can pick something and continue on before all
of the information has even been calculated.  (In fact, with some
cleverness, it can even automatically cancel any pending requests if
the dialog is closed -- see below.)

The implementation of the Monotone object is a mild pain, but that's
sort of inherent in anything that's doing IO.  Think of it as, well,
if you instead linked to monotone, you'd have to set up a thread to
call all the monotone API functions that block; having it in a
different process replaces the pain of threading with the pain of
process management ;-).  Maybe something like:

class Monotone : QMumble
{
    class Request : QRutabaga
    {
      public:
        // override in subclasses
        virtual void get_command(vector<string> & cmdline) = 0;
        virtual void process(string const & output) = 0;
        // every subclass has only one signal they emit
        bool canceled;
        Request() : canceled(false) {}
      protected:
        virtual void disconnectNotify(char const *)
        {
            canceled = true;
        }
    };

    class GetBranches : Request
    {
      public signals:
        got_branches(set<string> const &);
      public:
        virutal void get_command(vector<string> & cmdline);
        virtusl void process(string const & mtn_output);
    };

    void schedule(Request * request);

  public:
    void get_branches(QObject const * receiver, char const * method);
};

void
Monotone::GetBranches::get_command(vector<string> & cmdline)
{
    cmdline.push_back("branches");
}

void
Monotone::GetBranches::process(string const & output)
{
    set<string> branches;
    split_into_lines(output, branches);
    emit got_branches(branches);
}

void
Monotone::get_branches(QObject const * receiver, char const * method)
{
    GetBranches * request = new GetBranches();
    QObject::connect(request, SIGNAL(got_branches(set<string> const &)),
                     receiver, method);
    schedule(request);
}


And the actual processing loop does something like:
  state 0: when there are no commands to run
    do nothing
  state 1: when no command is running:
    if the first item on the queue has canceled == true, then discard
      it and go around again
    when you find one, get its command line, format it for stdio, and
      send it to monotone, then go to state 2
    if you don't find one, go to state 0
  state 2: while a command is running:
    just accumulate its output into a buffer
    when the output finishes, go to state 3
  state 3: when a command finishes:
    pop the first item off the queue, this is the request that just
      finished.  If it has canceled == true, then discard it and the
      output, and go to state 1
    otherwise, call request->process(output), then discard the request
      and go to state 1

And schedule() just puts something on the queue, and also checks to
see if we're in state 0, and if so nudges us into state 1.


Umm, anyway... you probably have something like that in guitone
already, and not only is this code untested but I don't actually know
how to use Qt.  Sometimes I get a little carried away with a nice
design challenge :-).  Perhaps it is useful, though?

-- Nathaniel

-- 
The Universe may  /  Be as large as they say
But it wouldn't be missed  /  If it didn't exist.
  -- Piet Hein




reply via email to

[Prev in Thread] Current Thread [Next in Thread]