[Raycast] Protobuf2TypeScript

A Raycast script which help front-end devloper to convert *.protobuf file into *.d.ts file.

Installtion Link

Screenshots

protobuf2typescript-1
protobuf2typescript-2
protobuf2typescript-3

Example

Convert this:

message Person {
  string name = 1;
  int32 id = 2;
  bool isFriend = 3;
  repeated PhoneNumber phones = 4;
}

message PhoneNumber {
  string number = 1;
  PhoneType type = 2;
}

message AddressBook {
  repeated Person people = 1;
}

into this:

interface Person {
  name: string;
  id: number;
  isFriend: boolean;
  phones: PhoneNumber[];
}

interface PhoneNumber {
  number: string;
  type: PhoneType;
}

interface AddressBook {
  people: Person[];
}

Idea From

Cocos 2.4.7 Building Assets卡住问题

关键词:构建资源、Building Assets、资源刷新、Prefab

背景

下午开发的时候想要构建一下游戏产物,但是一直会卡在资源打包上,白思不得其解,搜了一下官方论坛也没找到相关的问题的解决方法。

定位

reset大法一个个回滚到指定的commit上跑一下构建,直到找到会卡住的那个commit再做分析

// 命令行构建
/Applications/CocosCreator/Creator/2.4.7/CocosCreator.app/Contents/MacOS/CocosCreator --path /Users/7gugu/Documents/gitlab/genshinImpact --build
  1. 切换到项目的根目录
  2. 修改CocosCreator的路径
  3. 修改–path路径(该路径是用于指定在哪个位置创建一个build文件夹并且输出构建产物)
  4. 在命令行中运行代码,看构建是否会卡住

问题

最后定位到是因为有一个图片的meta文件不是被直接删除,而是被覆写,怀疑是这里覆写的时候Cocos没监听到文件变化导致Prefab没更新到,最后导致构建卡住。

解决

彻底删除这个被覆写的meta文件,重新添加图片文件即可修复问题。

延伸

翻查了一下git status,显示的是有一个文件被R052了,后面搜了一下stackoverflow才知道原来这个含义是覆写了52%。

Untitled

https://stackoverflow.com/questions/53056942/git-diff-name-status-what-does-r100-mean

如何解决Merge主分支代码导致本地代码被删除?

背景

之前需求发布的时候,把代码发布到了预发布分支上,但是产品突然发现一个体验问题阻塞了发布,需要先revert掉,等改动完成后再继续发布。

现状

改动完成后,需要同步一下主分支上的代码,在本地解决冲突后再合入主分支。此时会发现Merged后将会把本地代码给删掉。

复盘定位

如图所示,其实问题的根源就是:主分支上的revert没有被撤销,导致合入开发分支的时候本地代码都被revert掉了。

解决办法

简而言之就是,撤销之前的主分支上的revert就可以让主分支上的代码合入到开发分支上又不会把本地代码删掉。相当于告诉git,主分支上这批代码不用被删掉了。具体步骤如下:

  1. 从最新的主分支上创建一个临时分支
  2. 切换到这个临时分支
  3. 在临时分支上revert(撤销)掉之前的revert提交
  4. 将临时分支合入到开发分支上,同步最新主分支代码
  5. 解决问题

JS 代理模式实践

背景

最近在工作中遇到了需要对Module中导出的方法统一做预处理,在特殊场景下需要增加阻断执行并提示开发者不应该在该场景中使用该方法的提示。

思考

这里一开始的想法其实是在每个方法的入口分别增加一个阻断的判断逻辑:

如果只有一两个方法其实这么做倒还好,但现在的问题是,我手上有30多个方法都要加这个阻断逻辑,手搓阻断逻辑实在是太傻逼了,这么加不知道得加到猴年马月去。

后面我又看到Module的export default,想着能不能在这里做一下文章。

研究了一下之后,其实还是可以搞的,大致思路如下:

在ES6中,javascript引入了代理函数,从而实现对象基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)[1]。通过利用其拦截的特性,我们可以借此在module上实现代理模式。在外部函数获取method的时候做一些特殊逻辑。

实现

通过该方法,我们可以优雅的在module层面对method进行统一的前置操作(比如数据上报、逻辑阻断等操作),提升开发效率的同时还能使得代码更加简洁易维护。

兼容性

通过查阅caniuse,可以说这个接口已经100%兼容可用了。另外由于ES5的特殊性,Proxy是没有完整支持的polyfill的,因此如果碰到实在不兼容的用户,就积极引导用户去升级机器吧,没必要去钻这个牛角尖。

Android外网基本上只有电视盒子还是在4.4.4,手机几乎找不到4.0的机器,就算有看着0.08%的用户占比感觉可以直接忽略了。

如何判断一个JS方法的兼容性?

背景

由于业务的原因,我们的H5页面必须兼容一些比较老的webview版本,因此在开发中难免会遇到不支持的JS语法,因此在这里总结了一个切实可行的方法来提高自己确认接口兼容性的效率。

方法

通过caniuse.com查询方法兼容性

例子

假如需要使用Proxy方法来统一代理所有的接口,但是我不确定系统兼容性,怎么办?

1.访问caniuse.com

2.查询对应的方法名

3.查询iOS的兼容性

4.查询Android的兼容性

5.如果查询到的最低支持版本在需要的版本内则可以使用,另外在使用前最好在方法外套一层try...catch避免出现不支持的意外情况

WebAudio使用小结🧭

背景

最近在负责游戏项目的开发,不免需要接触到音频相关能力的开发。在移动端实践后才了解到,iOS下的AudioDOM并没有被完全实现,比如iOS下就无法直接通过JS控制AudioDOM的音量大小,只能控制其是否被静音,另外静音拨杆的优先级比音量键的优先级要高。如果用户通过拨杆打开了静音模式,AudioDOM默认是没办法控制音量的。

尝试解决问题

由于需要实现通用能力中规划的音频的响度控制,搜索一圈后发现可以引入WebAudio来解决问题。(https://stackoverflow.com/questions/27296391/is-there-any-possibility-to-control-html5-audio-volume-on-ios

改造前音频架构

初始化阶段的时候,通用组件会检查DOM树是否存在AudioDOM,如果不存在就会尝试创建一个新的AudioDOM并挂载到DOM树上。状态操作也是直接对DOM树上的AudioDOM操作。

改造后音频架构

WebAudio支持多种方式创建音频输入,即可以直接通过AudioBuffer获得音频输入,也可以通过createMediaElementSource从已有的AudioDOM上创建音频输入。此处我使用的是后者,通过在AudioDOM与扬声器之间创建了一个“代理层”,将音频输出通过GainNode(增益器节点)处理了一下。通过GainNode我们可以设置音频的响度,间接的实现了iOS的音量控制。

错误上报

不幸的是,测试的Testcase还不够完善,业务被发布后,错误量激增,告警拉满😂。

经过梳理后主要是以下三种问题:

  1. Failed to construct ‘AudioContext’: The number of hardware contexts provided (6) is greater than or equal to the maximum bound (6).
  2. The provided value is non-finite
  3. undefined is not a constructor (evaluating ‘new(window.AudioContext||window.webkitAudioContext)’)

1.AudioContext只能创建6个实例

Webview版本小于66中只能创建小于等于6个的AudioContext,会导致页面无法继续播放音频。(https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)

解决办法:

1. 增加全局AudioContext池,通过一个实例来管理最大音频对象数量。

2. 不使用的AudioContext需要被及时释放,避免占用音频对象资源。

2.音频节点提供的不是有限值

在某些环境中,JS的除运算计算出来的值不是一个有限值,直接赋值给WebAudio的节点会报该错误。

解决办法:

赋值前通过isFinite函数检查是否是有限值,如果不是则使用一个固定的值作为兜底即可。

3.window.AudioContext不存在

按道理来说AudioContext在执行JS代码之前就应该被挂载到window上了。但遗憾的是,iOS15之后的WKWebview会经常出现,执行JS代码的时候AudioContext仍然未被挂载到window上,导致执行构造函数的时候报错。

解决办法:

捕获错误并且定时重试,一般第一次失败的2-3秒后就能创建成功了。

如何在Apple Watch上添加Loading动画?

背景

去年11月的时候,买了人生第一支Apple Watch,因为内心涌动的软工魂,我又开始琢磨开发一个属于自己的iOS App。之后顺理成章的推出了自己首个WatchApp,《wTodo》一个用于在Apple Watch上管理微软待办事项的工具应用。

无原生组件怎么办?

由于App需要经常性联网,因此不免需要引入Loading态来告知用户当前的运行状态。不巧的是watchOS中提供的组件是阉割后的版本(主要是为用户的续航考虑,如果直接套用手机端那一套组件,watch的续航会受不了的),导致watch端并没有原生的组件能够实现Loading动画,这也让我苦恼了很久。后面下班摸鱼的时候,偶然在stackoverflow上看到了解决办法。

帧动画

如标题所述,在watchOS上只能通过“帧动画”来曲线救国,实现Loading动画。在这里给大家推荐一个来自Stackoverflow高赞中推荐的动画库。

https://github.com/mikeswanson/JBWatchActivityIndicator

该动画库提供了两种帧动画样式,第一种是类原生的Loading帧动画,如果个人比较偏爱原生动画的话,可以使用这个,直接点开“common image”就可以找到不同尺寸,不同帧率的帧动画了;第二种则是通过作者提供的工具,自定义动画的样式,如果想要自定义则需要clone项目到本地,然后通过Xcode打开项目运行到Simulator中即可,调整完参数后,点击生成即可获得符合自己要求的帧动画素材。

如何使用?

这里用类原生动画举例子:

1.从Common Image中选择自己需要的尺寸和帧率

这里我选择Normal Size 15帧的帧动画图片

2. 将帧序列图片拖到WatchKit App的Assets.xcassets文件夹中

3.在StoryBoard中引入Image组件

4.绑定Image组件到Controller上

5.设置Image组件参数

使用startAnimationWithImages后就会开始播放帧序列动画了,因此可以用来看作是动画开始函数。

6.停止播放帧序列动画

总结

至此只要在需要使用Loading动画的地方配置对应的Image组件即可给页面加上Loading态了。实现方法非常简单易用,就是国内文档太少了,只能去社区里找略麻烦了一点。如果希望自己去寻找更多内容的话,可以使用“loading indicator”去搜索,感觉会比Loading Animation说的更准确一些。

PS:

不知不觉就到8月底了,8月的碎碎念我还没来得及写,上周优化了wTodo加入了LoadingIndicator,这周就把加载动画的技术文章尝试沉淀下来了。如果下周有时间的话,我再花时间把碎碎念给肝出来吧。工作日加班真的消耗完我写代码的精力了,到了周末感觉都不想再碰电脑了,只想出门逛一下散散心。(真的不是我咕咕咕啦!)

[Discode] 生成 & 识别Discode

简介

书接上文,在上一章节中,我详细的分析了市场上已经落地应用的部分商用方案,也针对自己的实际需求进行了思考,设计了适合需求的编码规范和编码形式。在本章节中,我将会分享一下Discode的生成和识别过程和技术细节。

目录

  1. 生成Discode
  2. 识别Discode
  3. Github仓库
  4. 编后语
  5. 版权声明

生成Discode

如何生成图形编码?

在前面的设计原型中,Discode中包含四个定位点的设计。按照设计Discode的定位点类似于微信小程序码的定位点,基础元素是由一个圆环和一个圆点组成的图案。

定位点

之后会将其等距离的放置在图形编码的四个角上,用于图形的定位,如下图所示。

确定好定位点之后,我们就需要生成我们的图形编码了。图形编码包含两个部分:

一、用什么规则来代表数据?

参考条形码的设计,我决定通过点与线来分别代表0和1。之所以会有这样子的想法,是我认为在之后的学习研究识别过程中,点与线的识别应该会有更多现成的代码可以参考学习,不用自己再花大量时间来研究。

基于这种想法,通过简单的编程在canvas上生成了上图的DEMO图形,拥有五个定位点与按照一定规则环状点线的编码区所组成的Discode。(之所以在图形的中心也包含一个定位点,主要是当时想用来确认圆心是否能够被计算正确,所以增加的辅助图形,在技术验证后就被品牌Icon代替。)

二、存储数据量要设计多大?

在之前的原型设计中,我曾经分析过小程序拥有多种容量的设计,比如36线、72线、144线等设计。通过增加线密度来增加图形的数据承载量。因此我也对此进行了一些简单的实验。

上图是每隔五度绘制一位数据的72线版本的Discode,可以很容易的看到,在内圈的1-3层,由于图形之间的间隙过小,很容易产生图形与图形间粘连在一块的问题,特别是第二圈右下角的多线段连在一块很难辨别的问题。

题外话

那为啥微信小程序可以支持72线?

通过观察微信小程序的设计规范可以发现,之所以元信息区并不是从最内圈开始的原因:就是为了解决72线可能会导致图形绘制过于密集,导致编码图形难以被识别和处理。而选择了往外移了几圈,才开始进行实质性的编码。

微信小程序码编码设计

对比实际编码出来的图形,我们可以发现,为了美观考虑,小程序码在元信息区往圆心方向的编码区其实只有36线,即10度才记录一个编码,只有在元信息区才开始使用72线,即5度进行一个编码。

那问题就迎刃而解了,要么就是将实际数据编码区往外移,要么就是减少单圈编码密度。最终我还是选择了减少单圈编码密度作为我的解决方法,之所以选择这个方法就是图省事,简单快速的解决问题,不然将编码区外移又需要耗费一部分的时间来重新设计编码规范,我着实是不想这么干。

增大到10度一编码后,图形的视觉效果就好多了,另外也部分程度上的解决了图形粘黏的问题。解决了编码问题后,我们只要将定位点、编码区组装起来就是我们设计出来的Discode了。

其实到这一步Discode就已经生成完成了,只要在中间预留的空白处填上我们希望填充的Logo即可。

识别Discode

识别Discode主要有以下的几个步骤:

  1. 使用Hough Circle获取定位点的圆心坐标

通过Hough Circle Transform获得四个定位点的相对于图片的位置信息。

  1. 通过四个定位点计算编码区圆心位置

接下来连接对角点,形成两条线段(红线与蓝线),计算交点位置。

如上图顺序对应0-3的四个坐标代入公式计算得出圆心坐标。

\( y= \frac{(y_{0} – y_{1}) \times (y_{3} – y_{2}) \times x_{0} + (y_{3} – y_{2}) \times (x_{1} – x_{0}) \times y_{0} + (y_{1} – y_{0}) \times (y_{3} – y_{2}) \times x_{2} + (x_{2} – x_{3}) \times (y_{1} – y_{0}) \times y_{2}}{(x_{1} – x_{0}) \times (y_{3} – y_{2}) + (y_{0} – y_{1}) \times (x_{3} – x_{2})}\) \(x = x_{2} + \frac{(x_{3}-x_{2})\times(y_{cross} – y_{2})}{y_{3} – y_{2}}\)
  1. 设置ROI(Region of interest)识别指定区域

Discode的编码起点如下图所示:

Discode从上图的矩形框中开始生成第一位编码,然后在相同半径内沿着逆时针方向(红箭头),生成接下来的编码,当一圈编码完成后就自增半径,开始第二圈的编码直到编码五圈为止。

而识别其实就是逆向这个过程,如上图设置图像的ROI到图像的编码开始点,然后逆时针一个一个识别,一圈一圈识别,直到识别完成。

  1. 使用Canny edge detection获得识别区的轮廓

在上一步中,我们设置了图像的ROI,接下来我们要做的就是计算这个ROI内的图形到底代表的是0还是1。

识别的核心原理:计算ROI区域内的图形的面积。

之所以我们可以这么做,是因为我们已知ROI的面积信息,我们也知道长线段占用的面积理应大于短线段的常理,基于这两个信息,我们只要计算ROI内图形面积,并通过经验设定一个阈值来判定是长线段还是短线段。知道线段类型后,转换成0和1就水到渠成了。

这里我们引入了Canney边缘检测,通过边缘检测获得ROI内图形的轮廓数据。

  1. 通过contourArea函数计算轮廓面积

获得轮廓数据后,我们就可以计算出ROI中的闭合图形个数以及总面积信息,之所以有时候个数会大于1,是因为有时候ROI会重复识别到上一个图形的边缘,导致到污染了识别区。不过由于这种现象仅仅发生于长线段的交接处才会发生,且数据影响不大,因此我也没有做进一步的处理。如果要继续优化,可以继续精细化ROI区域,使得覆盖率提高。或者调整生成图形算法,加大线段之间的间隙,避免互相粘黏。

  1. 判定图形代表0或1

通过多次的实验可得,短线段的占用的面积必定小于10,大于10的必定是长线段。因此在这里使用10作为阈值,用于判定ROI内的图像是代表0或1。

  1. 分割 & 转换

将每个图像代表的二进制数值存入数组当中并将其按照6bit为一位通过编码字典,重新转换成可阅读的字符串。

  1. 输出结果

由于有时候编码的字符不一定会用完全部可用编码位,因此还会在有效编码后面加入一些随机数据作为填充,使得图形更加美观。

Github仓库

https://github.com/7gugu/Discode

仓库中包含了完整的生成和识别图形代码,可以自行运行学习一下。实测通过FireFox 100是可以正常运行的。

编后语

至此整个Discode系列就全部更新完成了。原来在这个部分我思考了很久,写了几个版本的内容,先是是过拆分成两个章节来慢慢讲解,也试过回溯历史结合QRCode来阐述为啥我要这么做,但效果都不尽如意,过于的繁琐冗余,最后还是秉着少即是多的原则,缩减成一章来讲解。五月中的时候恰好碰上了组内的技术分享,有幸向其他同事分享了我的这个想法,也得到很多宝贵的建议。之后由于毕业设计和工作上的琐事,使得进度很慢,对此向期待这篇技术分享的朋友说声抱歉。接下去我应该会专注于wasm和三维建模上,希望以后可以投入游戏产业,继续实现个人梦想。

版权声明

知识共享许可协议

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

【17.0已解决】独立 Watch App 真机调试无网络的问题

问题

现在我正在开发一个独立的 Watch App 应用,但是我发现在模拟器上调试时是可以正常连接网络的。但是当我上传到真机时,就会出现无网络连接的问题。具体的报错如下:

2021-11-20 21:42:27.156338+0800 wTodo WatchKit Extension[1068:2225475] PDTask <29C198EA-480A-459F-B5B9-421D9C26C7D8>.<3> finished with error [-1009] Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataPDTask <29C198EA-480A-459F-B5B9-421D9C26C7D8>.<3>, NSLocalizedDescription=The Internet connection appears to be offline., _kCFStreamErrorCodeKey=50, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataPDTask <29C198EA-480A-459F-B5B9-421D9C26C7D8>.<3>",
    "LocalDataTask <29C198EA-480A-459F-B5B9-421D9C26C7D8>.<3>"
), NSErrorFailingURLStringKey=https://baidu.com, _kCFStreamErrorDomainKey=1, NSErrorFailingURLKey=https://baidu.com}

测试机型

Apple Watch S7

系统版本: 8.1

网络环境: WIFI

原因

在watchOS 8中,GPS版的Apple Watch是不能单独设置APP的网络的,他会镜像iOS侧的App网络设置,比如设置支付宝可以用Wi-Fi,则Watch侧的支付宝App也可以用Wi-Fi。现在陷入的一个僵局仅仅是存在于新推出的独立App,独立App顾名思义就是只存在于Watch侧,iOS侧是没有App的。因此也不存在镜像iOS侧App网络设置的可能。这导致了如果App默认被设置成了不能联网,就会导致用户没法更改App联网状态(因为iOS侧和Watch侧都不能更改App的网络设置)。

解决方案

  1. 彻底关闭手机
    该方法是为了让 Apple Watch 不去同步手机的网络设置(默认关闭网络),促使独立应用能够正常联网,但该方法仅适用于网络连接少或者无的独立应用,比如小说、阅读器等。
  2. 开发 iOS 配套应用
    如果 iOS 侧有配套应用,则用户就可以在 iOS 侧对应用的网络权限做控制,进而实现 Watch 侧应用联网,该方法适合对于网络请求较多的应用。

PS:这个 Bug 的出现真的是很无语,花了我一整天的时间才解决,心累啊😣。

2024/02/17更新:

在watchOS 10.3.1 iOS 17.0的环境下,独立APP已经可以在“蜂窝网络”中看到“仅用于手表的APP”选项,在这里可以单独配置WatchAPP的联网能力。

[WP插件] WPComment2Bark

背景 🏞

最近大半年都在搞实习和雅思,其实没做多少实用的工具出来,有点手痒痒了。因此接着博客重建的契机,动手搞了一个评论信息推送的小插件。

思考 🤔

技术指标:

  1. 高触达率 🚀
  2. 开箱即用 📦
  3. 高度安全 🔐

解决方案:

1. 邮件推送

原来的推送方式就是通过Email的形式来推送,有可能会出现消息推送不及时或者被拒信,无法满足高触达率的技术要求,故摒弃这种推送方式。

2. Server酱

Server酱年初也因为各种外部原因,降级成了企业微信推送,其实不是特别方便,用户还得去装一个企业微信,然后配置Bot,再去配置APIKEY。对于我们做开发的用户来说,已经是挺繁琐的步骤了,对于普通用户而言简直就是噩梦。无法满足开箱即用的要求,故放弃。

3. Bark

最后我将目光投到了Bark身上,Bark是V站的一个dalao搞的一套利用Apple消息推送机制做的Web信息推送框架。Bark也同时满足我们三项技术要求:

1. 高触达率

借助Apple推送机制,我们甚至可以在息屏的情况下,都能正常收到推送消息。无视任何垃圾回收机制,绝对在线。

2. 开箱即用

用户只要下载Bark客户端,博客安装插件,配置插件,即可投入实际使用。

3. 高度安全

Bark提供免费服务器的同时,也提供了源代码供用户进行审查。如果是对于隐私比较敏感的用户,还能选择通过Docker部署自己的私有推送服务器。

综合上述优点,我选择了使用Bark作为消息推送的核心功能支持。

作用 🏄🏼‍♀️

每当有人评论你的文章时,可以推送到你的 Bark App。

配置指南 🧭

1. 从AppStore下载Bark客户端

2. 上传 & 安装插件

3. 配置推送链接

首先从客户端上复制出推送API和API密钥

第二步,切换到博客后台,依次点击【设置->讨论】,滚动到底部,找到【Bark推送设置】

至此就完成了全部配置工作,只要有新的评论被发出,就会调用API想您推送消息。

WordPress 插件市场

https://wordpress.org/plugins/wpcomment2bark/

插件仓库 ⛺️

https://github.com/7gugu/WPComment2Bark

点击【Code -> Download ZIP】下载压缩包后,按照配置指南,一步一步的安装即可。