2008年01月10日

在前面的Blog中我已经介绍了关于让按键处理更精确,并且如何当按下一个键后延迟一段时间的处理,那么基本想法就是:在向Queue中添加按键信息的同时,增加当时的时间戳,然后在线程中循环读取这个Queue,直到没有数据为止,然后比较按键时间与当时时间,如果时间间隔不到预设值,则进行循环。当下一次循环时首先从Queue再进行读取,如果有新的按键信息则重新开始计算时间,如果没有,则继续判断是否到了预设时间,如果已经到了,则进行相应的按键的处理。那么为了在读Queue时不阻塞,我是使用了非阻塞的读取,因此需要自已来做循环。

让我们再整理一下需求:需要一个Queue,用来放按键信息,然后当用户录入时,可以不停地放到这个Queue中。有一个线程可以读这个Queue,如果Queue中有从个消息,则全部读出,然后只保留最后一个。当存在最后一个消息时,需要等待一个时间段,如果时间段到达,并且再没有新的消息时,执开始执行处理。

整个处理代码都在modules/AsyncAction程序中。上一次改进后的处理代码为(有删节,去掉了些不是重点的代码):

    def __init__(self, timestep=.1, interval=0.05):
        super(AsyncAction, self).__init__()
        self.q = Queue.Queue(0)
        self.setDaemon(True)
        self.lock = thread.allocate_lock()
        self.stop = False
        self.running = False
        self.timestep = timestep
        self.last = None
        self.interval = interval
       
    def put(self, obj):
        self.q.put((obj, time.time()))
       
    def run(self):
        try:
            while not self.stop:
                if Globals.app.wxApp.Active and not self.q.empty() and not self.running:
                    self.lock.acquire()
                    while 1:
                        try:
                            obj = self.q.get_nowait()
                            if obj:
                                self.last = obj
                        except:
                            break
                    self.lock.release()
                if self.last:
                    if not self.running:
                        if time.time() – self.last[1] < self.timestep:
                            continue
                        self.running = True
                        try:
                            self.do_action(self.last[0])
                            self.last = None
                        except:
                            pass
                        self.running = False
                time.sleep(self.interval)
        except:
            pass
           
    def do_action(self, obj):
        pass

从上面可以看出,在调用put放置消息时,会自动加上当时的时间,然后在线程中会对这个时间进和检查。同时对于Queue的读取是采用非阻塞方式,因此需要自行做循环。

那么我发现,如果考虑使用Queue的带超时时间的阻塞处理方式的话,可以更加简化代码,并且由于阻塞,可以减少不必要的线程的循环,应该可以对资源的占用更少,那么改进后的处理代码为:

class AsyncAction(threading.Thread):
    def __init__(self, timestep=.1):
        super(AsyncAction, self).__init__()
        self.q = Queue.Queue(0)
        self.setDaemon(True)
        self.stop = False
        self.timestep = timestep
        self.last = None
       
    def put(self, obj):
        self.q.put(obj)
       
    def run(self):
        try:
            while not self.stop:
                self.last = None
                while 1:
                    try:
                        obj = self.q.get(True, self.timestep)
                        self.last = obj
                    except:
                        if self.last:
                            break

                if self.last:
                    try:
                        self.do_action(self.last)
                        self.last = None
                    except:
                        pass

        except:
            pass
           
    def do_action(self, obj):
        pass

从上面可以看出,put时不再计算时间了,因为真正的时间的作用是对最后一个消息有效,并且它的作为是为了延迟,所以只要在读取Queue时,采用阻塞方式,并且设置超时时间为预设值,当已经没有可读取的数据时,读取会阻塞一段时间后返回,这时就是一个合适的处理时机了。如果用户没有输入,那么线程将阻塞一段时间,然后再次阻塞。如果有输入,会立刻读出来,减少了循环处理,同时减少了循环所带来的开销。所以现在在UliPad中就是使用这种方式了。

2008年01月08日

昨天与ygao的交流中还谈到关于模板的处理问题。在UliPad中,在输入特殊的内容时,如果有相应的录入模板的配置,它会显示一段模板,如输入def空格,会出现:

def (${2:}):
    ${0}

这样的东西。这就是一个模板。那么在前面我认为ygao的对录入速度的计算会有效提高处理效率和防止不必要的处理。而模板在ulipad与自动补全是在一起处理的,即先处理模板,再处理自动补全相关的内容。具体的可以参见InputAssistant.py程序。那么在模板出现的问题上我们产生了分歧,ygao认为模板与自动补全功能不同,处理也不应该相同,对于模板应该是必定出现,不必要考虑录入速率。而我认为模板的处理与自动补全虽然功能不同,但处理模式相同,用户的体验应该相同,所以统一考虑即可,这样也不用做什么优化。因为要独立出来的话,需要修改代码,反而比统一处理改动要多。

不过我俩的观点很难融合,所以ygao提出能否做一个分支,至少不要让想法只停留在想法上,先做出来再说。我同意了。于是建议他成为ulipad的开发者。这样ygao终于成为ulipad的第一个除我之外的开发者。感谢ygao对ulipad的支持。

昨天又对UliPad的自动完成进行了完善。之所以要完善它,还是起源于ygao的一个patch。那天在网上碰到ygao(他可是为ulipad做了不少的事情,而且都很深入),他说到有几个优化的地方,其中有一个就是当输入速度比较快时,可以不弹出自动完成窗口,当时我就解释说ulipad已经支持了。直到昨天看到了他提供了patch,明白了他的处理方式。

我原来的做法是这样考虑的:用户录入速度有时不希望被打断,于是ulipad最好可以支持,当用户录入比较快时则不显示弹出窗口,直到用户停顿一下,表示他在等待,这时再进行处理。于是我通过线程方式来进行处理。首先有一个队列,然后在所有按键的事件是都会向这个队列插入按键的信息,然后有一个线程一直在运行,它会间隔一个比较短的时间。当它执行时,如果在队列中存在内容,它会取出最后一个,从而丢掉前面所有的信息,这样前面的按键信息将不会被处理,而最后一个会进行处理。可是这样的确存在问题,最重要的就是最后一个键出现后,没有一个延迟时间的判断,这样要么此时线程活过来,很快就执行这个按键的处理,要么正好线程在睡眠,需要等线程苏醒才能处理。也就是延迟时间根本不可控。

那么ygao的patch解决了这个问题。他是在每个事件处理中记录下执行的时间,通过精确比较两次按键的间隔来判断是否要向队列时插入按键信息。但是他的方案有一个问题就是当你输入很快时,即使我停下来,最后仍然不会弹出窗口,必须要在最后放慢录入速度。但是这种录入很难控制。我想最关键的问题就是他的方式只比较了这次与上次的间隔,但是最后一次按键的延迟没有进行处理。为此事,我俩在gtalk上还进行了争论,甚至有些激烈,不过总归是技术讨论。最后我的想法是将两种处理进行合并,我想这样就会好多了。同时ygao的方案因为要计算时间间隔,同时增加了一个输入速度的参数值,更可以精确控制,而且减少了不必要的线程执行开销,速度会更快一些。

最终我的修改是也增加一个时间的计算,就是使用time模块的time()函数来记录当时的时间。但与ygao的方法不同,我将时间的间隔不是放在键盘事件中进行判断,而是仍然放在线程,通过队列的方式进行处理。这样一方面可以仍然丢弃掉过快的按键信息,只保留最后一个键,同时在线程中可以计算当前时间与按键的时间,从而精确控制延迟时间,而不是只要激活就执行。这样会减少一些线程的执行,从而使整体录入速度加快。

现在svn中就是改后的版本了。

2008年01月07日

现在使用好看簿的次数明显的多了起来,主要是可以方便上传一些关于UliPad的图片,并且配以录音进行讲解。在使用中也发现好看簿的日志写起来还是很方便,虽然功能很简单,但是随手记录一些想法还是有意思的。这个日志就象是Twitter的功能。那么这就产生一个问题,存在两个内容源,怎么办。于是我想到了rss的烧制功能。那么Feedburner好象是用不了了,而FeedSky不知道谁把我的名字给用了,于是试着使用了下yahoo的pipes服务。功能的确很强,还有可视化的操作界面,不过感觉就是速度慢了点。结果做出一个rss的feed:

http://pipes.yahoo.com/pipes/pipe.run?_id=qN22A8_83BGS8bHN8SvLAg&_render=rss

很不直观,也不容易记忆,不过可以通过它同时阅读我在不同地方的日志了。

2008年01月05日

目前UliPad的一些功能截图已经开始放在好看簿上去了。好看簿的一项特别重要而又有特色的功能就是图片可以录音,我想这绝对比简单的文字和图片要更方便和有用得多。不过一直还没有尝试过,今天特意录制了一些说明,感觉还不错,有兴趣的可以去听一下。 http://www.haokanbu.com/group/4/

有些还没有录制。

2008年01月04日

What is Indent Moving Functionality?

Sometimes we need to move cursor through class methods or functions
quickly, and how to
do that? I found I can do this according the indent level. So I’v
finished this funcationality according
the indent level to quick move cursor.

Alt + Up Moving cursor to previous same indent level line
Alt + Down Moving cursor to next same indent level line
Alt + Left Moving cursor to parent indent level line, that’s the
indent level should less than current indent level
Alt + Right Moving cursor to child indent level line, that’s the
indent level should great than current indent level

And I think this feature maybe useful.

You should update from svn.

地址在: http://hi.baidu.com/lix%5Fxu/blog/item/16137ac793b6e0d8d000600f.html

这篇blog介绍了如何让UliPad在Linux下运行时也可以显示内存大小的技巧,不过要修改程序,我已经给作者留言,希望可以加入UliPad中。记得前不久,在ChinaUnix上也看到有一篇Blog是作者扩展了php.acp的功能,这样更方便php的代码提示,我给作者留言后,作者同意将他的成果放在ulipad中,非常感谢他的工作。Blog地址在:

http://blog.chinaunix.net/u/8570/showart.php?id=382744

现在使用UliPad的人多了一些,也有一些开始对ulipad进行定制,这是一个好现象,希望有更多的人把ulipad作为工具,并且把他们的心得分享出来,也使得ulipad更好用。