2016年11月26日 星期六

minecraft 創作:莒光樓

小弟是2198T 的退伍金門兵,當兵時放島休,自然會到金門島上四處走走,覺得莒光樓是個不錯的建築題材,退伍後花了一點時間在minecraft 裡面蓋了一座,斷斷續續的蓋,工期大概6個月。
在陰間沒相機,只能畫畫草圖,幸好google 找圖片就有很多人拍的相片可以看,庭園的部分用google 地圖也有實景可以「鍵盤旅遊」,很多細節都是這樣補齊的。

外觀好像沒什麼好說的,照著照片依比例選用磚塊、雕紋磚塊、玻璃等打造,室內擺設除了關鍵位子的盆景可以放之外,其他大部分都只有掛畫,都快變畫廊了。


二樓外部裝飾,同樣用Banner 掛在spruce fence來實作,缺點是超級浪費染料,染料也不像方塊挖一挖就有了,用掉一堆骨粉跟打一堆草,紅染料需求大產量又小,打草打到OOXX
後來才想到伺服器上其實有人蓋了鐵人農場,紅花都用不完_(:з」∠)_
其他部分:
藍旗 + (白染料 + 磚塊)+ (紅染料456)
藍旗 + (白染料 + 磚塊)+ (紅染料456)+ (白染料123)+(黃染料789)+(白染料468)

莒光樓的匾的部分,寫字真的就無法了:
白旗 + (藍染料741/963)+(藍染料123)+ (紅染料456)
藍旗 + (紅染料456)+(白染料258369)+(藍染料123)

屋頂的藍色後來發現是海中prismarine 的顏色最像,其他部分就用spruce 跟dark oak 來打造。

遠眺福建省……嗯……算了當我沒說,只會看到巨大農場……

天花板大概是最麻煩的東西了,原本的天花板是雕梁畫棟五彩繽紛,如照片所示,這在Minecraft 裡面根本做不到,只能用各種色彩的羊毛意思意思一下;只能說這中華民國美學你敢嘴?像素太低了沒辦法啦(yay

庭園的部分,沒什麼就是一直種樹,超級花時間;在實際莒光樓,沒人會去坐,不知道幹什麼的樓梯,都靠這個幫我算圓形怎麼蓋
http://donatstudios.com/PixelCircleGenerator
圍牆的部分就選用End Stone Brick ,顏色跟質料都很像:


花園蓋得比需期得還要大了一點,原本估計的正方形 72x72,後來蓋到快110x110,大超多owo,幸好server 上有安裝自己寫的fastbuild (自肥一下),在整地、填土跟挖建材上省了很多時間。
https://github.com/yodalee/FastBuild

來張全景:


建築物蓋完用shader 來一張是一定要的:
正面:
後面:
 

這個建案大概就到這邊了,還有很多細節部分無法盡善盡美Orz
金門其實是個漂亮的小島(只要不是當兵的話),歡迎大家去金門旅遊,小小建案希望大家喜歡owo

2016年11月21日 星期一

利用lxml 實作高效率的parser

最近實作facebook message viewer 的時候,需要去處理相當大的html 檔案,原始檔案大小約50 MB,beautify 之後會加到近80 MB。
實作上我用lxml 來實作,用了最基本的寫法,最直覺而簡單的寫法:
from lxml import etree
parser = etree.HTMLParser(encoding='UTF-8')
root = etree.parse("largefile.htm", parser)
問題是什麼呢,這樣lxml 一開場就把整個htm 給載到記憶體裡面,消耗一堆記憶體。
我們執行一下,看到的結果是這樣:
group count 525
parse 280895 entries
./parser.py   25.80s  user 0.21s system 99% cpu 26.017 total
  avg shared (code):         0 KB
  avg unshared (data/stack): 0 KB
  total (sum):               0 KB
  max memory:                710 KB
  page faults from disk:     0
  other page faults:         181328
710KB!根本記憶體不用錢一樣在浪費…,我把這個丟到網路服務上,光記憶體就害自己被砍了。

要打造節省記憶體的 xml parser,相關的資訊都來自這篇:
http://www.ibm.com/developerworks/xml/library/x-hiperfparse/

文中提供了兩種方式,一種是在parse 中指定我們的target class,我們要實作一個class 包含下列四個method:
1. start(self, tag, attrib): 有tag open的時候會呼叫這個method,這時只有tag name跟attribute 是可用的
2. end(self, tag): tag close 時呼叫,此時這個elem 的child, text都可以取用了
3. data(self, data): 在parser 收到這個tag 中的text child的時候,會將text 變為作為參數呼叫這個method
4. close: 當parse 結束時呼叫,這個method 應該回傳parse 的結果:
之後取用
results = etree.parse(target=TargetClass)
results 就會是close method return 的結果,這個方法不會消耗大量記憶體,不過它的問題是每一個遇到的tag 都會觸發一次事件,如果大檔中之有少量elements 是想剖析的,這個方法就會比較花時間;我的案例比較沒這個問題,因為我要剖析的內容幾乎橫跨整份文件。

另一個方法是用lxml 本身提供的 iterparse,可是傳入指定events的tuple跟一個有興趣的tag name的list,它就只會關注這些tags 的events:
context = etree.iterparse(infile, events=('end',), tag='div')
for events, element in context:
  print(elem.text)
文中並有說,iterparse 雖然不會把整個檔案載入,為了加速整份檔案可能被多次讀取的狀況,它會保留過去element 的reference,因此剖析會愈跑愈慢;解決方法是在iterparse 中,取完element 的資料就把它給刪除,同時還有element已經剖析過的sibiling,在iteration 最後加上這段:
element.clear()
while element.getprevious() is not None:
  del element.getparent()[0]
要注意的一個是,使用iterparse 的時候,要避免使用如getnext() 方法,我用這個方法,有機會在剖析的時候遇到它回傳None,應該是iterparse 在處理的時候,next element 還沒有被載入,因此getnext 還拿不到結果;從上面的end 事件來說,每個iteration,就只能取用element 裡面的元素。

改寫過之後的parse 的執行結果:
group count 525
parse 280895 entries
./parser.py   22.59s  user 0.07s system 99% cpu 22.669 total 
  avg shared (code):         0 KB
  avg unshared (data/stack): 0 KB
  total (sum):               0 KB
  max memory:                68 KB
  page faults from disk:     0
  other page faults:         21457
跟文中不同,因為我有興趣的tag 比例較高,執行時間沒有下降太多,但記憶體用量大幅下降。

另外文中也有提到其他技巧,像是不要用find, findall,它們會用XPath-like expression language called ElementPath。
lxml 提供iterchildren/iterdescendents 跟XPath,前兩者相較 ElementPath速度會更快一些;對於複雜的模式,XPath可以經過事先compile,比起每次呼叫元素的xpath() method,經過precompile 的XPath在頻繁取用下會快很多,兩種寫法分別是:
# non-precompile:
xpathContent = "//div[@class='contents']"
content = root.xpath(xpathContent)
# precompile:
xpathContent = etree.XPath("//div[@class='contents']")
content = xpathContent(root)
下面是我實測的結果,在都是完全載入記憶體下,有無precompile 的速度差接近兩倍,不過這也表示我用了iterparse 其實速度是下降的,因為我iterparse 其實是有使用precompile 的:
./parser.py 14.99s user 0.35s system 99% cpu 15.344 total
./parser.py 25.82s user 0.40s system 99% cpu 26.232 total

以上大概是使用lxml 時,一些減少記憶體用量的技巧,記錄一下希望對大家有幫助。

題外話:
在寫這篇的過程中,我發現Shell script 的time 其實是一個相當強大的指令,除了時間之外它還能記錄其他有用的數值,像是 memory usage, I/O, IPC call 等等的資訊。
我的time 是用zsh 下的版本,我也不確定它是哪裡來的,用whereis 找不到,用time --version 也看不到相關資訊,但man time 有相關的說明,總之copy and paste from stack overflow,把我的TIMEFMT 變數設定為:
export TIMEFMT='%J   %U  user %S system %P cpu %*E total'$' \
  avg shared (code):         %X KB'$'\
  avg unshared (data/stack): %D KB'$'\
  total (sum):               %K KB'$'\
  max memory:                %M KB'$'\
  page faults from disk:     %F'$'\
  other page faults:         %R'
其他的變數選項可以在man time 的format string 一節找到,這裡就不細講了,算是寫code 的一個小小收穫吧。

人生第一次參加 hackthon

故事是這樣的,最近手邊在寫一個網頁相關的facebook message viewer,然後…嗯…我根本不會寫網頁:前.端.超.難 。
正好這時候,學校的開源社在校內舉辦 hackthon,覺得這是一個大好機會,可以認識其它強者幫忙我作這個project,於是就報名了。

這是我第一次參加hackthon,想說先從比較小的 hackthon 開始,累積一點經驗,看有沒有機會參加玩真的hackthon,畢竟如隔壁棚qcl 大神都已經在參加真的黑客松,還遠征芬蘭大殺四方,嚇得老外屁滾尿流,在大神面前我根本只是業餘來亂的,不努力追一下怎麼行?

在hackthon 之前,我已經著手在這個project 一段時間,把後端的parser跟資料庫都寫完了,在hackthon上面只要開API跟寫前端就好了
第一天首先上台簡報一下自己想做的東西, 結果就徵到一名隊友,傳說中的昆立大神,雙方溝通一下API的格式;然後他埋頭寫了一下下,就用Vue.js 把前端整個寫完了,現在的user interface 坑全部都是隊友填的。

哇靠這到底是什麼神速,太強請受小弟一拜 m(_ _)m

然後…試著的所以我的Code 到GAE 上面,結果它用掉太多記憶體跟資料庫,記憶體是我parser 沒有寫好, 總之不付錢的話就沒辦法執行,啊我就是沒錢嘛咬我啊= =...
記憶體的問題,把parser改過之後就解掉了;資料庫真的就無法,資料量就是這麼多,最後放棄直接先睡了。

第二天為了demo ,決定直接把後端的parser 跟資料庫都從GAE 上請下來,整個改成local server,伺服器跟資料庫用Bottle.py 跟sqlite3重寫。
因為GAE 的 ndb 和 sqlite 的介面幾乎不同,平常也沒有在寫SQL 的相關的Code ,第二天重寫後端就用掉一整個早上,幸好到了下午2點左右,全部修完之後作品就會動了,上台demo 也得到不少迴響,第一次hackthon 就這樣堪稱順利的結束了。

我覺得參加hackthon,在參加前最好先有個題目的雛形,自己也可以先寫一些,這樣到了會場,一早 demo 過點子之後就能立即的尋求幫助和建議,遇到的問題也能問人,很快的得到答案。
到了hackthon 現場,就是專注在寫Code的上,在那個專注工作氣氛下,工作效率相當高,像我在第二天,很快的就把SQL相關的語法還有bottle.py 摸熟了,也為了趕出成品,第一天晚上,把我原本消耗超過600 MB memory 的parser重寫成只需要60 MB,算是把工作壓到兩個全天的成果。

當然現在的code 滿雜亂的,需要好好整理整理才行,最後的成品大概就是這樣,已經可以爬訊息跟去瀏覽特定時間的訊息:


這次hackthon 的氣氛算滿輕鬆,也有很多玩笑話,在這裡節錄一些語錄:

A: Steam 雖然出很多單機遊戲,但還是提供了成就系統……
B: Steam 根本是理財遊戲吧XD
C: 遊戲收集遊戲(XDDD

某:前後端分離?前端起手式:Bootstrap

某:Golang寫起來跟C 有 87 %像

2016年11月18日 星期五

使用GAE 回應fetch API

最近在寫GAE 上面的服務,把東西存進資料庫之後,開點API 讓使用者拿資料;這感覺就好像先蓋個大水槽裝滿水,然後在上面打洞讓水流出來讓使用者們喝(X
因為都2016 年了,被人說別再用XMLHttpRequest,趕快用fetch API 吧,就來研究一下GAE 要怎麼服務fetch API。

fetch API 用起來很直覺,對著一個網址 fetch 下去就是了,我開了個API 叫fetch,就是這麼直覺,回來的東西會丟進then裡面處理:
fetch("/fetch?type=data").then(funciton(response) {
  console.log(response);
}
其實概念是一樣的,fetch 就是去取某一個位址,所以我們可以開一個fetch 的handler,然後把對fetch 的請求都導到那邊去,然後建一個handler:
app = webapp2.WSGIApplication([
  ('/fetch', MessageFetchHandler),
], debug=True)

class MessageFetchHandler(webapp2.RequestHandler):
  def get(self):
    reqType = self.request.get("type") // reqType == "data“
剩下的就是handler的事了,想回什麼就回什麼,文字就文字,binary 就把binary讀出來寫回去,像是:
self.response.write("response message") # return text
with open("img/servo.png") as f:
  img_content = f.read()
self.response.content_type = 'image.png'
self.response.write(img_content) # return binary
一開始的時候,因為我有用GAE 的user API,在寫fetch 的時候,我的fetch API都會被redirect 到login 的網址,後來發現fetch API 預設不會包含必要的資訊,以致GAE 查不到已登入的資訊,要修好就是在fetch 時多加一點東西
fetch 提供了 Headers(), Request()函式來建立fetch,fetch 並可代入init 參數做更多設定。
例如我這邊的狀況,使用自定義的Init 參數,設定credentials 為same-origin,GAE 就不會再把我們導向login了。
另外也可以設定如mode, cache 等設定,比使用XMLHttpRequest來得彈性。Header則可建立request的header,例如content-type,詳細的內容可以參考後面附的參考文件。
var userInit = {
  method: "GET",
  credentials: "same-origin" };
var userRequest = new Request('/fetch?type=user', userInit);
fetch(userRequest, userInit).then(function(response) {
  if (!response.ok) {
    console.log(response);
  }
}

以上大體就是關於GAE 回應fetch API 的寫法,再來就是寫各種API 了。

相關資料:
https://developer.mozilla.org/zh-TW/docs/Web/API/GlobalFetch/fetch
https://developer.mozilla.org/zh-TW/docs/HTTP/Access_control_CORS
https://fetch.spec.whatwg.org/#concept-request-credentials-mode

2016年11月9日 星期三

整理一些增進效率的shell 工具

最近整理了一下自己工作上有在用的一些shell 工具或指令,在這裡分享一下,也許能讓大家工作更有效率。

shell:

沒錯不要懷疑,就是shell,如下這篇shell 本身就有不少快捷鍵可用:
https://blog.longwin.com.tw/2006/09/bash_hot_key_2006/
不過其實不少快捷鍵都有更好記的單鍵可代替,例如到行首尾可以用Home 跟End,平常比較常會用到的其實只有:

  • Ctrl + W 刪除一個詞
  • Ctrl + R 後打關鍵字,可以找上一個拿關鍵字的命令,持續Ctrl + R 持續找,光是用Ctrl + R 就海放只用↑↓在找指令的了(X,不過用了下面的fzf ,它會用fzf 代替單純的 Ctrl + R搜尋
  • Ctrl + L 可以清除畫面,跟下clear 是一樣的效果,而且clear 打起來超級快,最近還在習慣用Ctrl+L;我覺得這個重要的是在gdb 裡面沒有clear 指令,要用Ctrl+L來清除畫面。

autojump

本指令感謝AZ 大神的推薦,一個記錄你cd 過的地方,以後可以直接jump 過去的指令
https://github.com/wting/autojump
主流的linux 都有套件支援了,安裝後在shellrc 裡面source 它即可
if [[ -e /usr/share/autojump/autojump.zsh ]]; then
  source /usr/share/autojump/autojump.zsh
fi
接著它需要一點時間train 一下,平常cd 的時候它就會把cd 的資料夾記下來,之後就能用
j foo
jo foo
前者cd到名稱含有或近似foo 的資料夾,後者直接開視窗的file manager檢視該資料夾。
也可以下多個參數,例如:
victim/alice/data
victim/bob/data
如果只下j data 可能會跳去alice的資料,但下了 j bob data 就能跳到後者
我記得我在寫minecraft plugin 的時候,差點沒被java 氣死,為啥source code 一定要藏這麼多層勒,光cd ../../../ 就累死了,那時沒用autojump 深感相見恨晚;後來寫一些包含不少小專案,還要source sdk 的android project ,這個指令也幫了大忙。

fzf

本指令感謝士銘大神推薦,fzf 是一個模糊搜尋工具,讓你列出很多的東西,然後模糊查找目標:
https://github.com/junegunn/fzf
目前還在習慣中,安裝的話透過它github 頁面上的script 安裝即可:
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
archlinux 則有收到repository 中,安裝repository 之後在shellrc 裡面加上
source /usr/share/fzf/completion.zsh
source /usr/share/fzf/key-bindings.zsh
最一般的使用方式,是把其他會吐出一堆東西的指令當作fzf 指令的輸入,fzf 會把輸出全列出來,然後讓你打字進行模糊搜尋,或者用上下選取,用一下就覺得快若閃電,絲毫沒有延遲,例如:
locate servo | fzf
find -type f | fzf
加了key-bindings.zsh 之後就連上面的Ctrl+R 也會變成fzf 模式,潮爽der;fzf 也有跟vim 綁定,不過這部分我沒試用,暫時先持保留意見。