笔记:NCurses和RubyCurses绑定研究二

思考:重复代码和编程技巧

朴素代码里表达的重复性,充满复制粘贴、以及他必须要在这时候出现。各种复制。 这种复制性,是合理的,是必然存在的。他摆在这个位置,他完成了,那么他就是必然的,应该的。

我们只能用代码技巧、用循环,把所谓的拙劣的、重复的代码,替代成引用、替代成循环生成。

编程技巧的目标就是 KISS 旨在去除重复,合归一处容易修改,尽量解耦减少影响。无他而已。

TUI 中 输入会带来阻塞么?

我之前自己尝试过写命令行,当时是自己使用puts完成的。

那么问题来了,当时的问题在于程序中, gets 会阻塞程序自身。那么这个问题在 Curses中解决了么?

至少到目前的例子中其实并没有。

目前的例子中,界面永远是先于 gets 这种操作进行的。并且我们知道 Curses中 getch操作会调用 refresh。

所以目前看来 Curses的程序工作流程是:

1.初始化屏幕 2.在内存中新建对象(window) 3.我们修改对象 4.发送绘制命令,把内存中对象,渲染到屏幕上 5.等待用户输入交互(4如果没有,5这步会代劳发送绘制)

所以整个还是阻塞的。

这对于我之前理解的更新在于,他在前面存在了数据结构就是内存对象。并且渲染到页面。他做到了这个操作。

重要

但是如果我想动态更新页面内容,我不得不在用户输入的那边,放一个外循环(或者吧整个界面绘制放入循环中)。 不断地等待检查用户的 getch 输入。

然后又不可避免的在各种用户输入下,进行对界面元素的 重绘、重排版。

在视觉上,编排界面甚至是读写对象,出现了至少2次 —— 一次最外层初始化、一次是在循环中。

重复的事情,我们可以通过循环、封装成一个对象,来减少书写的代码。

但是它依然是简陋的——他是循环+不断更新。

当然我们也知道了一些额外信息,就是 Curses可以做到修改了才更新。我们针对对象使用refresh可以减少算力浪费。

但是我们自己的程序,将会在一个外循环里,分割出计算时间。

至少两次,谈谈逻辑守恒(我发明一个词)

渲染一个内容,但是这个内容可以被数据驱动改变。那么必然至少有2次调用。 我们换4个方式去做这个:

1.硬写代码,那么就把绘制代码,放在头一次,改变处写一次。

2.循环代替渲染,外部初始化一次,内部循环处写一次。这样还可以更新N次。但是书写还是需要2次。

3.把绘制代码,用函数封装。 两次写的地方,均可以用一句话比如 drawComponent 代替。 但是 drawComponent 还是需要出现2次。

4.如果我们创建了自动绘制的数据结构,而绘制工作,可以和一个声明式接口相关,比如 React中的 setState。调用了,就会向框架内部发送一个更新的指令。最外侧循环接收到了任务会开始重新渲染。那么只需要在 初始化、和更新处调用2次 setState。依然需要2次。

不论形式如何演变,但是 至少2次。这是不会变的。我们只能把一种形式,变成另一种形式。可以用循环、可以用封装、可以自建数据结构…… 但是逻辑性不会消失。

代码依然保持着自我的逻辑性存在着。不会被消灭 —— 逻辑守恒。 程序的本质不灭,只是形式在变化。

屏幕、循环维持

屏幕的展示,我理解其实是不需要循环维持的。类似于51单片机的电灯。我们把需要展示的内容,放入显存,通电,显存里面有 010101010,自然根据显示器的电路来点亮等。 只要供电存在。这部分是不需要额外算力的。(当然这是理想模型。这个模型的特点在于,我们不需要关心屏幕)。

程序需要关心的是,一旦发生变化,生成新的内容,写入 Buffer,当组装好了数据,调用一次 refresh这种开关,把 Buffer内容写入到 显存。

而程序交互,无非来自于 外部的IO(包括用户输入、网络接口….) 你只能等待。

所以 while循环,它是重复。他在外循环里,即表示内部的内容重复几遍。当他内部内容是 getch 这种,那就意味着持续等待用户输入。

一旦用户输入,就开始出发 对应的调用,然后写入Buffer….

是否可以说,交互性的东西一定是在等待输入的,一定是阻塞的。

只不过,心理建设上,这种等待,不是卡在程序中间阻塞后面所有程序。而是所有初始化程序执行完,该渲染的渲染,在最后一步阻塞,等待交互。一旦用户输入进入一轮新的循环。

交互产生分支,分支逻辑处理完毕,有空闲了,继续等待。

关于是否会错过交互? 这个问题

一定会有的。从Vim的卡顿、Emacs的卡顿,哪怕是App、Web网页。其实都一样。他们必须在一个周期里完成任务。 所以React自己完成了调度,他保持了每个周期里,一定有一段可以响应用户的时间。

人类的行为是慢速的,所以才能和这套东西配合起来。人类点击的行为时间远大于处理分配的间隔时间。那么他就仿佛每一刻都在等待我们操作。

但是如果我们的程序执行慢速到超过了某个间隔时间。用户的操作将会被忽略掉。这就是我们日常看到的一些卡顿问题所在。

如果处理期间,不断地发出指令。他就会被忽略掉。 这里有很多改进的门道。也许可以用队列缓存。也可以计算密集的交给 工作线程处理,我们依然要让程序有时间响应用户操作。

我们自己的程序其实不存在并行,除非引入线程、进程

上面隐藏有一个疑惑就是,我们自己能否制造出多个并行的程序。来减少卡顿、阻塞。

那么我们就需要构建一个循环,在加入一个队列。队列允许压栈放入任务,不同的任务可以依次、交叉进入队列执行。执行完切换上下文。我们需要设置循环彼此等待……

等等,如果我们每个程序都这样做。是不是有点太浪费时间了。而现实中,我们的程序往往就是单线逻辑。所以我的一个猜想或者想法是。

1.我们自己的程序在系统视角是顺序执行的。

2.如果我们希望自己的程序(多个)可以并行。

我们可以自己构建调度系统。但是其实大可不必,操作系统里面就带了调度系统。更简单思路是—— 借用系统的调度系统(这其实是一个公共需求)。

然后一旦使用线程、进程。相当于我们脚本里面这段的程序,他在时间序列上、执行的从属上会和主线程并列,共用时间。

在系统的大循环里,做到了平级。大家的程序都在系统的大循环里,某个队列里等着。进入交叉执行——时分复用看起来就像并行任务。

3.有价值的意义

对于执行任务,顺序执行 ABC,就可以了。

对于交互程序,A代表交互,BC代表工作任务,我们实际上需要执行的是 ABCABCABC…. 而如果 ABC是固定顺序,BC时间过程会影响交互。

利用线程,我们就可以理解为 A B C 三个并行。交给操作系统调度。A永远可以交互,只是在等待 B、C结果的到来。

实际时间肯定加长因为存在系统调度时间,但是 A 程序永远可以在一个单位时间内轮询你、响应你。这就是最大的意义。

而A的内部可以实现一个简单的队列,接受命令。然后交给工作线程,A只要等待结果展示。这个闭环的交互,是可用的。这是意义。

所以要引入线程、进程这个东西。

然后我理解,他是把我们的顺序任务,丢入一个大循环里共享时间分片。提高了同步处理的效率(A B C轮着执行,各自推进,都有进度。而不是 A-B-C 顺序等待)。

疑问?

如果我想做更多的任务,我想界面非阻塞的自动刷新。那么我应该如何做呢?

1.目前线程是我能想到的。

主线程负责循环等待交互,和每次刷新界面。我们可以创建 worker线程,他负责在背后的计算,两个线程之间,通过队列通信。

主线程、工作线程,他们在时间序列上是并行的,然后被操作系统的外循环控制 —— 相当于时间分片,并且彼此交叉执行了。

2.还有新的方式么?

Mark24

Everything can Mix.