Sucha's Blog ~ Welcome

20年10月21日 周三 20:51

ffi_gccload 在 Lua 中动态加载 C Source

不晓得从哪里搜到了这个库 lua-tcc,可以在 Lua 运行期间,通过 one pass 的 TCC 动态加载 C 代码,让 Lua 的控制可以更深入底层,甚至可以在 Lua 中进行 C 的编程。

可惜这个项目距离久远,TCC 在 MacOS 下面编译也成问题,意味着通用性欠佳,挺可惜的。不过有了这个想法后,想着不通过 TCC,用 GCC、Clang 其实也可以的嘛,于是就有了 ffi_gccload

流程变成了这样,将 C Source String 导入到一个源文件里面,通过 GCC 编译得到动态库,使用 ffi.load() 加载进来,并通过 ffi.cdef 声明接口,就可以使用了。

其实就是将 C 的编译、链接过程给固定了就好,如下的代码:

local config = require("ffi_gccload").new()

-- like ffi.cdef
config:addSourceDef([[
    int print_name(void);
    int add_num(int a, int b); 
]])
local p = config:loadSourceString([[<a id="include"></a>
 <stdio.h>
int print_name(void) {
    printf("Hello, world\n");
    return 0;
}
int add_num(int a, int b) {
    return a * 2 + b;
}
]])
if p then
    p.print_name()
    local a, b = ...
    a, b = a and tonumber(a) or 0, b and tonumber(b) or 0
    print("result", p.add_num(a, b))
else
    print("failed to load")
end

生成一个动态编译配置,会输出

$ lua test.lua 2 3
Hello, world
result  7

其实是将编译产生的中间 .so 放在临时目录,加载后其实就可以删除掉这个 so 了,这在 MacOS 以及 Linux 应该都是可以的。

上面生成的动态编译配置,其实可以加入 include、libary 的 path 以及 lib name,已经越来越像一个 build 系统了,其实就是类似的。

有了上面这套,虽然相比 TCC 速度慢一点(毕竟 TCC 是 one pass 就出成果的),但是胜在兼容性号,修改一下后,估计 Windows 下面也是能跑的。

往后可以在服务端拿到 C 代码,捣鼓以下就能用到客户端上面了,或者一些少量使用 C 操作更方面的逻辑,结合放在 Lua 中作为源代码发布出去,使用的时候,先编译加载动态库,也是一个方案。

20年10月11日 周二 02:12

DNS service 重构

将 rpc_framework 中 service_dns 网络层的控制从 C 层移到了 Lua 层,C 层只负责 UDP 包的数据拼接、解包,将代码统一到了 m_dnsutils 里。

在减少了大量 C 跟 Lua 的交互后,两者算是各自做了自己擅长的方面,自测后的效果也不错。

20年9月29日 周一 18:25

Rust(1)

开始学习 Rust,从 rustlings 入手,可以看中文版的 Rust 程序设计语言。看了两天书,边看边写习题,完全当成一门新语言来学,内容太厚重了,而且对于我自己已有的经验来说,跟我以往了解的语言,大不一样。

其实 Rust 无需垃圾回收的所有权(borrow)部分,为了防止多线程竞态条件的部分,有 C/C++/Java 多线程经验的话,是很好理解的。Rust 虽然也有指针,以我的理解,因为编译器可以确切知道具体引用情况,是不需要多写 *p 这样的了。

深入骨髓的不同,从编译器、语言设计角度来说,是模式匹配、None、trait、宏,字符串、迭代器。

模式匹配

模式匹配不仅仅只是 match 关键字,及其控制流,从 let 到 if let 都有模式匹配的影子,如下

let tup = (500, 6.4, 1);
let (x, y, z) = tup;

上面就是元组的模式匹配,如果数量或类型对应不上,编译期就抛错误。而 if let 是为了简写 match 出现的。因为 Rust 自己的错误处理机制,其实大量用了 if let 或者 match,所以模式匹配一定会遇到的。

Rust 这里还深入提到了一点,有些模式匹配必须成功,程序才能编译通过,比如上面的例子,下面 Some(T) 在其实也是模式匹配,是可以接受匹配失败的。

None

跟很多语言不同,Rust 的 None 不算是一个值,而是一种状态,是我的感觉。下面两个是语言固定了的,一定会遇到

enum Result<T, E> {
    Ok(T),
    Err(E),
}

pub enum Option<T> {
    /// No value
    None,
    /// Some value `T`
    Some(T),
}

Result 用于表明结果是否正确,正确的使用 Ok(T) 返回,错误的用 Err(E) 返回,具体的值需要解包才能使用。Option 表示要么有值,要么没有值,Option::None 没有别的作用,就只是表明没有值而已,如果是 Some(T) 返回的,也是需要解包才能用具体的值。

Lua 的 nil 是 false 含义,还能塞入 table 表示 array 终结,ObjC 的 nil 也是 false 的含义,还能初始化指针,C 里面的 NULL 同理,Java 里面的 null 也是经常用来比较的,还容易引发 NullPointerException,反正是一个大量使用的合理值,新世代的 C++ 我不了解了,但 Rust 不一样,None 就是没有值,是一个枚举定义,我们经常使用且想见到的是 Some(T) 包裹的值。

trait

Rust 没有继承,跟 C++/Java 不同,其实鼓励使用组合来完成功能,认为两个不同实例拥有继承关系,因此隐含了大量重复的代码是危险的行为。trait 跟接口很像,但 Java 的 interface 还可以定义函数行为,Rust 这里就不可以了。而且 Rust 教程里面也不会使用 interface 这样已有的词汇来描述 trait,因为 Rust 还有非常严格的类型系统,如果需要做容器的话,会用到很多 trait 提供的能力,比如 Box dyn 之类的。

trait AppendBar {
    fn append_bar(self) -> Self;
}

impl AppendBar for String {
    //Add your code here
    fn append_bar(mut self) -> Self {
        self = self + &"Bar".to_string();
        self
    }
}

上面为 String 增加了 append_bar 的接口,添加固定的 "Bar" 后缀。

因为 Rust 的编译器需要在编译期知道所有变量的大小,对于容器来说,是通过 Box 包裹来描述。Rust 为了实现零成本的抽象,泛型的处理实际上很多时候是编译期展开的,只有一些特殊的情况才是运行期动态决定的,这里我了解不深,先略过。

Rust 的宏跟 C/C++ 的 define 完全不同,因为不懂 C++ 的 template,所以也回答不上是否拥有 template 这么强大的元编程能力。同样作为元编程,能力强是真的,这个部分我还看不大懂,大概了解到,Rust 宏在 AST 构建完成后才展开,能捕捉下面这些元素

item: an item, like a function, struct, module, etc.
block: a block (i.e. a block of statements and/or an expression, surrounded by braces)
stmt: a statement
pat: a pattern
expr: an expression
ty: a type
ident: an identifier
path: a path (e.g. foo, ::std::mem::replace, transmute::<_, int>, …)
meta: a meta item; the things that go inside #[...] and #![...] attributes
tt: a single token tree

举个例子,下面 vec! 宏捕捉了 expr,push 每个 x

let v: Vec<u32> = vec![1, 2, 3];
<a id="[macro_export]"></a>

macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

例子都来自 The Little Book of Rust Macros

字符串

Rust 内建了 str 类型,表示字符串的纸面含义,是输入字符串原来的值,String 是一个 std 类,内部是 UTF-8 编码的字符串,char 在 Rust 实现是 4 个字节的 UNICODE。

做习题的时候,经常性的 String::From("abc") 或者 a.to_string(),习题里面的 char 倒是很少用。

感觉字符串在哪一种语言都不是一个简单的事情,现在都是全球化了,考虑字符串是一种输入,可能是任何一种语言,因此其长度、分割、排版显示都不是小事情。

迭代器

Rust 中每种容器都有迭代器,即便是 (0..10) 表示 0 - 9,(0..=10) 表示 0 - 10,可以下面这样用

pub fn factorial(num: u64) -> u64 {
    if num <= 0 {
        0
    } else {
        (1..=num).fold(1, |acc, x| acc * x)
    }
}

assert_eq!(24, factorial(4));

上面的斐波那契数列计算,没有用到多余的变量,没有用到 for、while、loop 和递归,只用了 Rust 里面的 fold 累加器,其他的还有 sum、filter、map 等,还有,上面忘了说的,expression 放到最后,就直接返回计算的值了。

--

举了好几个跟我之前了解的编程语言大不相同的例子,其实除此之外,还有不少,一些是我还不了解的,比如生命周期,比如 unsafe pointer,生命周期的描述是可以根据一些 trait 控制来改变的。Rust 性能优先,还考虑能高效调用 C 接口,unsafe pointer 是在 Rust 的借用模型、寿命周期外,自己控制程序的行为,毕竟需要调用 C 的接口等等。性能优先,因此零成本抽象、宏等等都是在这个基础上展开的,动态的部分不能说没有,只能说场景很少了。

这两天的心得,大概是这样吧。

20年9月26日 周六 23:07

ffi_bitcask.lua

写了一个 bitcask 模型的 key/value store ffi_bitcask.lua。利用了 LuaJIT cdata memory layout 来写每一个 record 前面 timestamp、crc、fid、ksize、vsize 部分,然后接着写入 key 及 value,读取的时候,先读取每个 record 的前面固定部分,得到 kszie 及 vsize 后,再读取 key 及 value。

ffi_bitcask 的模型,db 目录下有不同的 bucket 目录,bucket 里面可以有相同的 key,bucket 类似于 namespace。每一个 bucket 只有一个活跃文件,所有的 SET/GET/DELETE 操作都是记录,添加到活跃文件末尾。当活跃文件超出阈值后,重新开启一个新的活跃文件,旧的文件是只读的。

我偷了一个懒,所有的活跃文件写入操作,实际上都有重新打开并添加数据,没有像网上介绍的 bitcask 模型一样,一直保留一个打开的文件指针,也许待后面写入速度影响之后,再做这个修改吧,目前自己使用,吞吐量还没到这个份上。

由于删除操作也是记录,原有的记录空间重复占用,只有在下次整理文件时才能回收。我觉得这里自己的算法不好,总共读取了两遍。因为先要扫描出来,哪些是需要删除的条目,并按照文件 id 分类,之后再做一次读取,过滤掉删除的条目,将其他不受影响的条目拷贝到一个新的活跃文件中。

如果有不少删除操作,而许久没有做回收操作,文件目录占用空间比实际使用要多上许多。我自己还加上了将回收后的文件 id 重新利用的逻辑,文件 id 绝对值的增长不会因此影响。

LuaJIT 的 FFI 定义 cdata,内存 layout 跟 C 是一样的,缺点就是 uint64_t 跟 uint32_t 混用的话,是按照 uint64_t 对齐的,另外 Lua 没有 unsigned number 的说法,如果需要用到这个,需要用到 bit.tobit 函数来做比较了。

目前 ffi_bitcask 还缺少的是 hint 文件,现在还不影响,后面可以考虑加上去。还有就是回收操作,其实可以每次只扫描新产生的文件就可以了,因为删除操作只会在之前扫面过后,新生成的数据文件中产生,不过这里需要记录哪些是新生成的文件,如果文件 id 只朝一个方向增加的话是挺好办的,但像我这样重复利用已经回收后的文件 id 的话,就不好处理了。

后面再看看吧。

20年9月19日 周六 23:57

Object Storage 和 Shell 环境变量

找了几个 Object Storage 的方案,理想中的可以是去中心化的方案,然而找不到,找到的要么是 IPFS,要么是链圈的,太高大上了。退而求其次,分布式的的方案,就很多了,各大云商家的对象存储,其实蛮便宜的,还有基于 minio 的,基于 Redis Master-Slave 的方案,及其变种的也算一类,还有豆瓣的 gobeandb,以及 bitraftlf,一个去中心化的 Key/Value Store.

因为有云服务器了,单独又开对象存储不大值得,因为需要存的东西不多,只是希望至少是分布式的,多个保全而已;minio 的方案,在分布式使用到时候,需要占用单独的磁盘,这个只好放弃了;Redis 及其变种 SSDB 之类的,底层 levelDB,或者占用内存多的放弃了,因为读写的频率很低,而且需要跟其他程序一起占用小排量的云服务器;gobeandb 犹犹豫豫很想用,但需要单独使用一个 goproxy 来分流感觉不大值得,接口是 memcached 的,手头也没有立即能用的解析器;bitraft 试用了一下,感觉最为接近,接口是 Redis 的,但是,无法基于不同数据中心做同步,我的两台云服务器位于不同的服务商它居然无法连接,就不说他默认 value 只有 64k 了;lf 一样没有趁手的接口,而且感觉不好配置。

bitraft 是最接近能用的,而且本地验证同步是很不错的,可惜了。

这里衬托了这么久,只是为了说自己最后写了一个,底层存储基于 Tokyo Cabinet,有现成的 Lua 绑定,网络服务接口用的是 rpc_framework 搭建的,同样基于 Redis 协议 lua-resp,同步方案现在很挫的了,一个 master,slave 主动同步 master,启动的时候同步全部 key(也许后面可以优化),然后每隔 30 秒同步一次最新的 SET/DEL 操作;master 这边是每 15 秒区分一个操作 group,操作 group 里面记录所有的 SET/DEL 操作,有 slave 请求,就将 group 时间点及其之后的所有分组 group 操作同步过去;slave 每次都同步上次最新 group 时间点之后的操作数据。

如果 master 这边留了足够多时间跨度的操作 group,后续即便偶尔连不上也是可以保证接下来的数据的;不过目前确实没有考虑 slave 间隔读取不到 master 操作的问题。

为了安全,还加上了 AUTH 命令,简简单单,基本能用了,VALUE 的长度,跟 REDIS 一样是 512M,基本够用了。

Tokyo Cabinet 据说在 2kw 左右的数据量后,会读写缓慢,现在距离这个点还早得很,而且很可能都遇不上。更好的,当然是使用 bitcast 做底层存储,可惜目前也没有方案。

先这样吧,代码就不公开了,单机版在 rpc_framework apps 里做 demo 了。

--

方案找了好久,差不多一周,试用了 bitcast 半天,最后决定用自己的半成品,大概写了 2 天,郁闷的是后面半天时间,每次自己手动启动就正正常常的,但是使用服务推上去就跑不起来,slave 变成了 master。

后来发现,是接受推送,拉起服务的 service,没有使用到包含最新区分不同云服务商的 shell 变量,所以 fork 后拉起来的服务,自然就缺少这个 shell 变量了;手动启动正常,是因为 SSH 进去后每次都读取最新的 bash 配置。

囧啊。

20年8月17日 周一 23:32

LuaJIT 下的双链表及 AVL 树

Lua 描述结构数据方面,只有一种内建结构,就是 table,table 既是 array,又是 hashmap,算是简洁高效的实现,但我们需要一种动态表示元素先后、大小关系的数据结构时,内建的 table 就不合适了,比如常见的 fifobinaryheap

先说优点,上面两个都是使用 table 来描述先后顺序、大小关系的,缺点是,当有插入、删除发生时,时间效率太慢了。其中 fifo 是使用 table 的 array 实现的,需要在删除元素后,对其他的元素做拷贝迁移,避免空位;而 bianryheap,也是使用使用 table 的 array,当元素位置变动时,采用了冒泡算法,对元素重新排位。

空间效率是保住了,但就时间效率来说,太不专业了,O(N^2) 的实现,数据量稍微多一点点,时间不能看。

其实 fifo 算是一种双链表,只是插入是在头尾,算是双链表的特例。双链表下,非头尾的删除发生,时间效率是 O(1) 级别的。其实可以用 table 来描述链表关系,缺点是空间消耗大,为了描述两个相邻元素间的先后顺序,就得用一个 table 实例来做。

我用 Lua 内建的 table 实做了一把,时间效率是很高效的,空间的话,插入 number,10,000,000 级别的插入,内存大概 6G。

-- performance
push 1000,000, cost 0.393088
pop  1000,000, cost 0.092984

在 C 的双链表实现中,需要将链表节点跟数据结构绑定起来,要么是以链表节点为主,链表节点指向数据结构,或者像 Linux Kernel 里面规范化的双链表实现,链表节点实例,放在数据结构中,通过偏移定位数据起点。

但是在 Lua 里面做不到这样,不得不使用 value(值)到 node(节点),以及 node 到 value 的两个 table 来做关系映射,再加上相邻两个 value 的顺序关系使用一个 node(table)来实现,也是空间消耗大的原因。

LuaJIT 下 cdata 的妙用

LuaJIT 因为有 FFI,可以创建结构化的 cdata 来描述相邻 node 之间的关系,而不需要使用内建的 table,使得可以花费更少的空间代价,比如下面这样来描述双链表的节点关系:

ffi.cdef [[
struct _cnode {
    struct _cnode *prev;
    struct _cnode *next;
    float key;
};
struct _chead {
    struct _cnode *head;
    struct _cnode *tail;
};
void* calloc(size_t count, size_t size);
void free(void *ptr);
]]

特别注明 cdata 是 calloc 出来的,不在 gc 管理范围,因为 C 下面的指针,需要保证指向固定的内存地址。其他跟纯 table 实现的双链表一样,需要两个 table 来维护 value 跟 node之间的映射关系,如上面 struct _cnode 下的 key,其实是用于 node 到 value 的 table 的映射 key,原因下面再说。

同样的 AVL 树也可以使用 cdata 来维护节点间的大小关系:

ffi.cdef [[
struct _avl_node {
    struct _avl_node *left;
    struct _avl_node *right;
    struct _avl_node *parent;
    int height;
    float key;
};
struct _avl_root {
    struct _avl_node *node;
};
void* calloc(size_t count, size_t size);
void free(void *ptr);
]]

以上使用 cdata 描述元素关系时间效率跟纯 table 时间效率一样,空间效率的话,第一个例子下,内存占用不到 4G,地址 ffi_list.luaffi_avl.lua

一些问题

LuaJIT 下使用 cdata 描述节点关系,遇到了一些问题:

上面的问题,估计还是要设计一些测试用例来定位一下才好,现在先这样吧。

后记

19 号解决了上面遇到的 2 个问题,原因在于 ffi.cdef 使用的 key 宽度是 float,但是 LuaJIT number 的宽度是 double 导致的,我修改 ffi.cdef node 里面的 key 为 double 类型,问题不再出现。

不过我也有疑问,为何 AVL 这边同样的实现,没有暴露出来问题呢。

反正现在修改为多轮循环 test 验证了,验证了几千遍,每次是 1000x1000 次的 push、pop,都是正常的了。

20年8月02日 周日 20:50

做了一个 web 框架:cincau

离职后在家呆了一个月,前面两周在休息,后面两周开始忙起来,想着将手头的数据搭一个可视化的网站出来,可以学习一下 web 前端的库之类的,但是呢,想找一个能 sqlite 和 nginx 的 web framework,找到一个感觉还可以的 Lor,但是呢,不支持 sqlite,感觉不算是一个 minimalist 的 web framework,就想着不如自己搭建一个吧。

可是如果搭建,顺便带上自己的 mnet 是肯定的了,当然 nginx 也是要支持的。于是参考了 lor 的不少东西,但更多的其实是自己摸索的,因为时间也比较充足,是先考虑了架构,才开始动手的。

没有浪费之前买的 goodnote 5,用来构思框架了,画了大概 5 个页面,大的模块上,跟 lor 一样,分为基础库,以及具体的项目代码。基础库在 /usr/local/cincau,用来生成基本的 demo 项目,本身包含了所有 proj 用到的的基础库。对上面的简单业务逻辑,封装 engine 层,不区分为 mnet 或者是 nginx,但实际上这两者一定是要区分开的,除非是静态的文件。

基础库里面,细分为 engine 层、router、MVC 逻辑、database、POST 的解析、HTTP Request 这几个部分,其中 MVC 中 view 的 rendering 用了 leafo 的 etlua,这其实是基于 openresty 的 lapis 的一个模版库。database 不出意外,我只考虑了 sqlite3,就是 minimalist 的 web framework,等有需要,在考虑 mysql、postgresql 之类的,毕竟这两者,没有基于 mnet 的接口,只有基于 nginx 的。

在 goodnotes 5 上面花了 3、4 天这样,其实上面这么多细节,都是在完成整个项目后,回头才细化出来的。在草图出来以后,框架搭建花了大概一周,mnet 是基于 ffi_mnet.lua 以及 hyperparser 的回调,拿到 http 数据结构层。而 nginx 是 content_by_lua_file 来驱动的。

然后还继续花了 3、4 天来完善框架细节,比如 POST 以及 HTTP request 的那一套,以及基于这个框架,写了一个 demo project,支持 ment、nginx,包含静态页面、mock 的 model,以及 post x-www-urlencoded 和 multipart/form-data 这几个部分。

该放地址了:cincau,话说之前也断断续续做过半个 web framework,现在终于点连成了线,水到渠成了。

goodnote 5 画出来的框架图:

20年7月25日 周四 11:22

Curl 的 Lua 绑定,以及 multi 接口

尝试了一下 curl 发送请求的 multi 接口,另外的 easy 接口是一个阻塞的设计,将 easy handle 放到 multi 中后,变成了非阻塞的接口。

大概是下面这样的逻辑,是非阻塞的,目前遇到的遗憾,是 perform 以及 wait 都没法知道具体是哪一个 easy handle 请求结束了。

easy = curl.easy()
easy:setopt(...) -- custom request header, response header, body reader
multi = curl.multi()
multi.add_handle(easy)
repeat
    multi.perform()
untile multi.wait() <= 0

另外还试了一下 curl 中的 event-driven 接口,其实也还是 multi,但是区别于 multi.wait() 基于 底层 select 的设置,这里的 event-driven,依赖比如 libuv、libevent,以及我想集合进去的 mnet 的 事件循环。

创建这种 event-driven 的 curl 程序,还需要需要底层提供一个 timeout 的回调,也许是创建 socket connect 后, 回调具体的 fd,有了这个 fd 才能将其加入到 epoll、kqueue 里面去。

但是我自己验证的结果,感觉程序上跟 github 里面的 example 已经很像了,但一些请求没能成功。

这种方式需要设置好几个回调,在这个基础上设计中间件太复杂了,不管是输入的 header、post data,还是回调拿到 header、 body data 都太复杂了。

另外这种 event-driven 的方式还有一些隐含的条件,比如 timer 是独立于 fd 的,在 fd 前面就创建了,而且 timeout 的单位是 micro seconds,这其实是一个精度很高的 timer 了。

这里我也有疑惑,比如对于 curl 来说,这里的 event-driven 只是借用了 epoll、kqueue 的内核事件机制,我不大理解为何需要由 curl 来 创建 socket,为何不是更底层来管理 socket fd,其实参数交给 curl 来设置都可以,底层只是保证非阻塞就可以了。而 curl 更多的是面向 HTTP 这一层的中间件,raw data <-> HTTP structure data 的转换。

但不管怎么说,curl 这套东西还是太香了,毕竟每个发行版都有,每个发行版安装 dev 或者 command line tool 之后,有了头文件,就可以立马基于 libcurl 这个库进行发开发了。

因为自测 event-driven 没成功,相关信息也比较少,最后是用了上面 multi 接口包裹 easy 接口的非阻塞式设计,用了 Lua-cURLv3 这个库,就请求而言,单独使用了 select。

20年7月25日 周四 11:22

HTML 的 Content-Type: multipart/form-data

研究了一下,Mac 下轮着 Safari、Chrome、FireFox 前后台调通后,这里大概记录一下吧。

前端页面 HTML

在前端 HTML 的写法是,form 里面指定 enctype 为 "multipart/form-data",如下

<form class="cell" action="" method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile" />
    <input type="submit" />
</form>

后端解析

假设在浏览器里面点击 "file" 按钮,选择发送的是一个 PDF 文件,协议上收到是下面这样的,... 是省略:

POST /path HTTP/1.1
...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5WeF4Lu8GyQKEIE5
Accept-Language: zh-cn

------WebKitFormBoundary5WeF4Lu8GyQKEIE5
Content-Disposition: form-data; name="myfile"; filename="noname.pdf"
Content-Type: application/pdf

%PDF-1.3
...
%%EOF

------WebKitFormBoundary5WeF4Lu8GyQKEIE5--

我自己没有试过多文件,但是从网上资料看,多文件跟单文件不同的是,前面一个 boundary 结束符后,再来一个 boundary 的 Disposition 描述分割,开启下一个数据块的传输。

代码部分就不贴了,感觉有点挫,考虑了多文件分割,以及需要传递给用户文件名、类型等信息,最后是使用了阶段状态的描述,毕竟后端收到的数据是不定长的输入。

--

最后还是整了一下代码,加入了 builder,在 multipart-formdata-lib.

20年7月16日 周四 12:04

LuaRocks with LuaJIT

标准版的 LuaRocks 只有原版的 Lua 可以配套使用,torch 为了让 LuaRocks 能够支持 LuaJIT,修改了一个版本,其实只是修改了编译过程,毕竟 LuaJIT 完整支持 Lua 5.1 的语法。

torch 的版本支持 LuaJIT 的各个版本,但最新的只到 2.1 beta2。早上我将 LuaJIT 的支持版本升级到了 2.1 beta3,地址是 luajit-rocks,然后还发了一个 pull request。但是这个项目有好几年没有更新了,不晓得会不会有 maintainer 会注意到。

torch 的 luajit 其实还有修改 2 个地方,一个是 string 的 hash 方案,一个加入了交互用的 readline。交互部分明显有改善,而 hash 部分不晓得为什么要这么改,应该也还好吧,反正为了 pull request 这里我是不敢动的。hash 用的是 tommy 的方案

在 Linux 中编译了一把,顺利通过,后面再看需要安装吧。在自己的 MacBook Pro 上安装好后,install 了 busted 和 moonscript。不得不说,其实是为了试一下 moonscript,硬是找到了 LuaRocks 的 LuaJIT based 方案。

先用一阵子再说吧。

20年7月15日 周三 22:38

MacOS 下使用 BitBar 在状态栏自定义信息

如果自己有一些信息需要通过点击状态栏图标后获得,或者通过点击状态栏图标启动、关闭的,都可以使用这个工具 BitBar 来操作。

安装好后,只需要定义 plugin 目录,然后在 plugin 目录下加入功能脚本就可以了。脚本其实只关注输出的文本,操作部分其实是通过特殊命令来定义的。

比如例子中的 refresh.sh,精简后是下面这样:

if [[ "$1" = "restart" ]]; then
osascript <<EOD
	tell application "BitBar" to quit
	delay 1
	tell application "BitBar" to activate
EOD
fi

echo "↻"
echo "---"
echo "Refresh Me| terminal=false refresh=true"
echo "Restart Bitbar| bash='$0' param1=restart terminal=false";

会在状态栏显示一个圆圈刷新,点击后下拉菜单会有 "Refresh Me" 以及 "Restart Bitbar"。

可惜的是我虽然弄懂了一点点如何编写这个 Bitbar 的 plugin 命令,但是通过 Bitbar 控制的程序最后没有符合我的预期,:-(