« September 2015 | Main | November 2015 »

October 26, 2015

扩展 lua require 的行为

今天同事提了个需求,他希望可以给部分 lua 代码(由策划编写)做一个沙盒关起来。在 lua 里做沙盒很容易,只需要控制函数的环境就可以了。不过另一个附加需求是,这些代码还可以直接利用 require 加载。

而我们又不想去修改系统的 api 接口,那么怎么做到这点呢?

首先, 我希望使用的时候看起来像这样:

local xxx = require "xxx" (myEnv)

和传统的 require 用法不同,可以在后面追加一个参数 myEnv 。这样的话,每次 xxx 模块被 require 时,它其实被重复运行一次,但会绑定不同的 _ENV

其次,既然模块会被反复初始化,那么我们甚至还可以约定,每个这种沙盒封装的模块还可以接收 require 的传入的额外参数。

做到这点很容易,我们只需要在 package.searchers 里追加一个自定义的 loader 然后并不返回加载的模块 chunk ,而是做一个函数封装。将 chunk 的运行推迟到传入 myEnv 调用之后。

这样,load chunk 本身还是依靠 require 的 package 机制缓存代码的,只是每次调用后,重新绑定 _ENV 生成了一组新实例。

我在 gist 上贴了一组代码实现这个特性:延迟绑定环境的 require

October 16, 2015

推荐款老游戏:维多利亚 II

维多利亚 II 这是本月我最喜欢的游戏了,有点老,也不算太老。最后一个 DLC 黑暗之心 是 2013 年出的。

在游戏刚出的时候,有一篇著名的介绍文章,推荐一读:维多利亚2(Victoria2):历史的逻辑 。我当初就是看了这篇文章知道这款游戏的,可惜当时没玩进去。经过几年的发展后,现在的版本比当初完善了太多了,所以不用在意文章中说的缺点。

下面是我在 steam 上写的评测


陆续买了 P 社很多游戏,都没安下心来玩。知道很好玩,但就是耐不下心来。维多利亚在刚出的时候就玩过,可能是因为没有汉化,没找到乐趣就放下了。这几天看到 DLC 已经出了这么多,网上找的汉化包据说也相当高质量了,就仔细体验了一翻。

结论:相当棒的游戏,如果想体验历史战略的感觉,可能目前找不到第二款了。我想推荐给所有没玩进去 P 社游戏的所有玩家,不要怕,你一定玩得懂的。至于 P 社的粉丝,肯定不用我推荐啦。

注意,一定要买齐 DLC ,尤其是黑暗之心,这样才是一个完整的游戏。不要图便宜只买原版,那会少太多乐趣。

这款游戏很特别,不像文明,有随机地图,还有玩家精心制作的地图 mod 。它只有一张地图,就是一战前的真实世界,但重玩价值非常之高。因为你可以从任意一个国家起手,体验到完全不同的乐趣。游戏把国家的政治经济外交设定的非常真实,而有保留了极高的自由度。游戏大部分按照规则推演却又不失真实。我查看过游戏的事件脚本,触发条件都是非常简单的,而在游戏中你又隐约可以感到那种历史的真实感,完全是拜精心还原的历史数值导致的,简直是太赞了。

也不像三国志,只有武将和战争。在维多利亚里安心种田的乐趣比四处争战还要有趣且充满挑战。

作为中国玩家,用中国起手会亲切的多。虽然游戏的原意是模拟欧洲的,玩中国会放弃大部分的精心设计,但依旧可以体验到那种独有的略带荒谬可又觉得有几分道理的,游戏中历史的真实。

在黑暗之心中,中国被划分成好几个次国家,我是用广西开始的。一开始非常无聊,不能外交、不能科研、不能造兵打人,即使把游戏速度开到最快,这个状态也要持续几十分钟。不过对于刚接触的玩家来说,可能也是件好事。因为你不会迷失在海量的交互界面中。你能做的只有调一次政党(或者不调),调整一下税率(其实就是一次把所有税收都调到最大,并关闭军事开销),把仅有的一个国家焦点设到某个省份上鼓励宗教人士直到发展到 2% (普及识字率)然后换下一个省。

在识字率和多元化都很低时,科技点涨的非常慢,且你不能自由研究科技。只能在政治界面上选有限的发展(消耗科技点)促进开化。这个几乎没有什么操作只能干等的时间,正好可以让你四处游走,点点那些不知所云的界面研究一下都是些啥。

大概点开五六个科技后(这里选哪些发展是最主要的变化因素),你就可以开化国家,然后进入真正的游戏进程了。西化一定会导致中国分裂事件,而你就会面临第一场战争:中国内战。所以你需要在快开化时,赶紧加大军备。由于游戏进程的不确定性,每一盘还会有所区别,因为邻国的发展是随机的,有可能和大清开战,也有可能和云南开战,这需要你事前评估。当然也可能你的军事很强大,而大清刚好在闹太平天国而无暇打你。

第一场战争会让你体验到 P 社独特的战略玩法,虽然很多人说在维多利亚系列里,战争不是重点,但我还是觉得挺好玩的,而且需要很上心的玩。一开始完全没明白规则,摸索了好几盘才有点头绪,后来索性去游戏 wiki 上读了大量的英文帖子。不过相信我,这些都不是必要的,你完全可以做到在游戏过程中学习。这款游戏的内核是及其复杂的,复杂到你可以完全不理会这些复杂点,也不会用人脑去计算所谓最优策略,而只用简单的操作去控制就好了,一切交给 AI 。玩不懂的时候,就当它是一款自动推延的放置游戏就好了,光看那些 AI 是如何勾心斗角的就很有意思了。多试几盘,或者一盘游戏无论玩的多滥都坚持玩下去(反正不会失败的)你自然能学会如果更有效的打仗,如何搞外交。

玩过几十小时后,虽然还没弄明白游戏中的各种细节,但也不会再怕那些复杂的表格了。接下来再试试用欧洲的列强起手,还可以试一下美国内战(DLC),相信会有各种新奇体验。

这绝对是值得原价入的游戏。当然,不急的话可以等打折 :D

October 14, 2015

给 skynet.call 加上超时

不断的有人问在 skynet 里怎么给跨服务调用加上超时处理。我不太想再解释为什么在 skynet 这样的系统中,加入超时机制会使得在其上构建的系统增加不必要的复杂度。那只是为了想不到更好的方法而做的一点变通。

你应把 skynet 视为一个整体,正如你平常所写的程序,大多数 api 调用的时候不会设置超时一样。只有在明确的特殊需求时,才在上层在封装一层带超时参数的调用(而不是直接使用 skynet.call )。

当昨天再次收到这个特性请求时,我仔细考虑了一下。发现了一个有趣的方案,可以非入侵性的在现在的框架下加上超时返回这个特性,下面做一个记录:

修改 skynet.lua 这个基础库很不划算,会改变很多基础的调度代码。另外写一套带超时的 call 又会和已有代码大量重复。其实,如果我们单独做一个代理隧道服务,专门转发请求,超时处理就会简单的多。

我们可以实现一个代理服务,它的作用仅仅是将外部请求转发给指定的服务,并将回应包转发回去就够了。在这个代理服务中可以设定一个超时值,一旦转发的请求在指定时间内没有响应,则给调用者一个 error ,并在真正收到回应时忽略掉就可以了。

我们甚至可以在这个代理服务中做许多额外的监测和统计工作,易于发现系统中有问题的设计。而代理服务将实际的服务包裹起来后,对调用者来说,是完全透明的。超时设置只在启动时(或写在配置文件中)设置,使用时,完全不需要修改原有的代码。

担心加一层额外的代理会损失性能是完全没必要的。代理服务几乎没有状态,可以直接用 C 语言编写。主要工作只是做消息转发,甚至不需要额外拷贝消息,而只需要传递指针。用 C 语言编写意味着内存开销可以忽略不计(没有额外的 lua vm ),而纯消息转发的时间开销也是 O(1) 的。

对于超时计时,如果需要不是那么精确的话,以秒为单位即可。我们不需要为每次调用都向系统索取一个 timer ,而可以做成一个时间轮结构。用一个低频心跳工作,我认为 10Hz 甚至 2 Hz 都够。每个心跳检查一下有没有调用超时了,若有,就抛处一个 error 。


暂时我不打算将这个特性加入 skynet 现在的版本(毕竟已经快发布正式版了),除非在我们自己的项目或有人大量使用它证明是有用的,可以考虑在以后的版本中收录。

我用 lua 做了一个简单的实现,放在 gist 上,有兴趣的同学可以玩玩。

https://gist.github.com/cloudwu/63c08b7d6f9be1d0950f

这里有两个文件,totun.lua 是我写的这个代理服务;而 testtun.lua 是一个简单的测试程序。

注意,我特地使用的 skynet 的核心 api 实现的 totun ,而没有使用高层的封装。这使得它可以很容易的翻译成 C 版本(如果有必要)。没有任何 coroutine ,只有对 C 函数的调用。

对 skynet 的基础部分工作原理有兴趣的同学,也可以阅读这份实现。搞清楚仅靠设置一个消息回调函数,skynet 服务是怎样工作的。

由于没有使用上层框架,它虽然是一个 lua 服务,但不能直接使用 skynet.newservice 启动。在 debug console 里也看不到状态,无法用 debug 控制指令控制。如果期望实用的话,还可以进一步做封装。

实际上这个代理服务并不要求一对一,而可以多对一的。即多个调用者对一个服务。它内部重排了 session 号以支持这一点。(如果是一对一设计,可以简单的转发 session 号即可)所以你可以按 service mgr 那样对所有需要代理的服务做统一管理,同一个服务只生成唯一一份代理,而不是在每个使用使用的地方都生成独立的代理。

October 13, 2015

埃及一日游

前段时间去希腊旅游,回程埃航的票在开罗转机,停留 10 小时。

原来计划并没有打算游埃及,所以也没有办埃及签证。感觉那边革命完没几年,社会应该比较乱,还是在机场待着比较安全。

结果一下飞机就被一工作人员领到转机口办手续,说是可以带去酒店休息。一想还有这么好事,可以淋浴睡觉,顿时对埃航好感度提升。由于就是在机场转机柜台办的,放心把护照交给他们了,按吩咐跟着机场的妹子去酒店。

原来以为房间就在机场里(因为没有签证,埃及也不是落地签,通常出不了海关),结果莫名其妙就走工作人员通道出去了,只是要把护照压在机场里。当时没多想,出了机场换了个人(确认还是机场的工作人员)说带我们去酒店。等车的时候问要不要去金字塔看看,一个人收 40 刀(车费),可以刷卡。想想既然来了,不去有点可惜就答应了。

一路上,他更像是个导游,各种聊天活跃气氛。路上有可以拍照的地方还都停下来让拍完照再上车,直到金字塔附近车突然就拐进一小巷子,把我们拉到一看起来不那么正规的骑骆驼游沙漠的店里。

后面就是各种挨宰了。由于连护照都被人押住了,感觉就是非法入镜。离机场又那么远,想想就认命了。看起来人家也只是求个财,最后前前后后被宰了几千埃镑(和 RMB 汇率差不多,略贱一点,应该和港币等值)。全部是刷卡的,最大头是骆驼游,一人要一千多。然后是强卖的阿拉伯头巾,一快破布要 150 。所有人都死命的要小费,给美元还不高兴,要欧元。由于钱包里没几张票子了,还要走了几张 100 的 RMB ,最后依旧不死心,刷卡的时候多刷了 200 。

btw, 一起有个欧洲人,不知道是没零钱还是真大方,小费一给就是 50 欧一张的。如果不是最后和我们一起回机场拿着护照重新入关,我严重怀疑他是个托儿。

不过抛开这些不爽的事情,既来之则安之,其他还是挺不错的。骑着骆驼在沙漠里晃荡了两小时。还爬了十多米金字塔外墙,直到被人叫下来。骆驼比马高大很多,背也相当宽,骑起来比骑马舒服多了。在导游的怂恿下,还试着在骆驼背上站了一会儿,感觉挺稳的。

一路上感觉埃及着社会从根子上都烂了。我另外给了 10 欧一人的门票钱,似乎并没有去正规的地方买票,而是从一后门溜进去的。沿路碰到许多穿着警察制服的骑兵,一路塞钱,还带找零的。

回程去了好几家特产店,感觉也是宰客的。我想了想,干脆装听不懂英文,也就混过去了。带我们的那个机场工作人员可能觉得不太好意思,请我们在加油站吃了顿便餐。

吃饭的时候随便聊了一下,他在机场工作了十多年。谈起前几年那事,他反复强调最近几年越来越好,比之前那是强太多了。


回家后在网上查了一下,果然埃及那果真是骗子横行。至于埃航的酒店福利倒是真的。不过最好是你坚持去酒店,然后安顿后自己想办法出来玩。金字塔那里不要搭地铁口的出租车,不要去这种不正规的骆驼旅店,不要买景区的任何东西。

我的个人建议是,10 年内都别去埃及旅游,直到社会完全建立起良好的秩序。


这次最大的感受是,我们这还是别乱了,其实现在挺好的。